dmux 3.2.0 → 3.3.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 (655) hide show
  1. package/dist/DmuxApp.d.ts.map +1 -1
  2. package/dist/DmuxApp.js +270 -1606
  3. package/dist/DmuxApp.js.map +1 -1
  4. package/dist/actions/implementations/closeAction.d.ts +10 -0
  5. package/dist/actions/implementations/closeAction.d.ts.map +1 -0
  6. package/dist/actions/implementations/closeAction.js +201 -0
  7. package/dist/actions/implementations/closeAction.js.map +1 -0
  8. package/dist/actions/implementations/copyPathAction.d.ts +10 -0
  9. package/dist/actions/implementations/copyPathAction.d.ts.map +1 -0
  10. package/dist/actions/implementations/copyPathAction.js +34 -0
  11. package/dist/actions/implementations/copyPathAction.js.map +1 -0
  12. package/dist/actions/implementations/duplicateAction.d.ts +10 -0
  13. package/dist/actions/implementations/duplicateAction.d.ts.map +1 -0
  14. package/dist/actions/implementations/duplicateAction.js +25 -0
  15. package/dist/actions/implementations/duplicateAction.js.map +1 -0
  16. package/dist/actions/implementations/index.d.ts +14 -0
  17. package/dist/actions/implementations/index.d.ts.map +1 -0
  18. package/dist/actions/implementations/index.js +14 -0
  19. package/dist/actions/implementations/index.js.map +1 -0
  20. package/dist/actions/implementations/mergeAction.d.ts +14 -0
  21. package/dist/actions/implementations/mergeAction.d.ts.map +1 -0
  22. package/dist/actions/implementations/mergeAction.js +84 -0
  23. package/dist/actions/implementations/mergeAction.js.map +1 -0
  24. package/dist/actions/implementations/openInEditorAction.d.ts +12 -0
  25. package/dist/actions/implementations/openInEditorAction.d.ts.map +1 -0
  26. package/dist/actions/implementations/openInEditorAction.js +33 -0
  27. package/dist/actions/implementations/openInEditorAction.js.map +1 -0
  28. package/dist/actions/implementations/renameAction.d.ts +13 -0
  29. package/dist/actions/implementations/renameAction.d.ts.map +1 -0
  30. package/dist/actions/implementations/renameAction.js +18 -0
  31. package/dist/actions/implementations/renameAction.js.map +1 -0
  32. package/dist/actions/implementations/toggleAutopilotAction.d.ts +10 -0
  33. package/dist/actions/implementations/toggleAutopilotAction.d.ts.map +1 -0
  34. package/dist/actions/implementations/toggleAutopilotAction.js +33 -0
  35. package/dist/actions/implementations/toggleAutopilotAction.js.map +1 -0
  36. package/dist/actions/implementations/viewAction.d.ts +10 -0
  37. package/dist/actions/implementations/viewAction.d.ts.map +1 -0
  38. package/dist/actions/implementations/viewAction.js +26 -0
  39. package/dist/actions/implementations/viewAction.js.map +1 -0
  40. package/dist/actions/merge/commitMessageHandler.d.ts +27 -0
  41. package/dist/actions/merge/commitMessageHandler.d.ts.map +1 -0
  42. package/dist/actions/merge/commitMessageHandler.js +123 -0
  43. package/dist/actions/merge/commitMessageHandler.js.map +1 -0
  44. package/dist/actions/merge/conflictResolution.d.ts +13 -0
  45. package/dist/actions/merge/conflictResolution.d.ts.map +1 -0
  46. package/dist/actions/merge/conflictResolution.js +134 -0
  47. package/dist/actions/merge/conflictResolution.js.map +1 -0
  48. package/dist/actions/merge/issueHandlers/index.d.ts +11 -0
  49. package/dist/actions/merge/issueHandlers/index.d.ts.map +1 -0
  50. package/dist/actions/merge/issueHandlers/index.js +8 -0
  51. package/dist/actions/merge/issueHandlers/index.js.map +1 -0
  52. package/dist/actions/merge/issueHandlers/mainDirtyHandler.d.ts +13 -0
  53. package/dist/actions/merge/issueHandlers/mainDirtyHandler.d.ts.map +1 -0
  54. package/dist/actions/merge/issueHandlers/mainDirtyHandler.js +72 -0
  55. package/dist/actions/merge/issueHandlers/mainDirtyHandler.js.map +1 -0
  56. package/dist/actions/merge/issueHandlers/mergeConflictHandler.d.ts +13 -0
  57. package/dist/actions/merge/issueHandlers/mergeConflictHandler.d.ts.map +1 -0
  58. package/dist/actions/merge/issueHandlers/mergeConflictHandler.js +52 -0
  59. package/dist/actions/merge/issueHandlers/mergeConflictHandler.js.map +1 -0
  60. package/dist/actions/merge/issueHandlers/nothingToMergeHandler.d.ts +7 -0
  61. package/dist/actions/merge/issueHandlers/nothingToMergeHandler.d.ts.map +1 -0
  62. package/dist/actions/merge/issueHandlers/nothingToMergeHandler.js +12 -0
  63. package/dist/actions/merge/issueHandlers/nothingToMergeHandler.js.map +1 -0
  64. package/dist/actions/merge/issueHandlers/worktreeUncommittedHandler.d.ts +13 -0
  65. package/dist/actions/merge/issueHandlers/worktreeUncommittedHandler.d.ts.map +1 -0
  66. package/dist/actions/merge/issueHandlers/worktreeUncommittedHandler.js +49 -0
  67. package/dist/actions/merge/issueHandlers/worktreeUncommittedHandler.js.map +1 -0
  68. package/dist/actions/merge/mergeExecution.d.ts +22 -0
  69. package/dist/actions/merge/mergeExecution.d.ts.map +1 -0
  70. package/dist/actions/merge/mergeExecution.js +184 -0
  71. package/dist/actions/merge/mergeExecution.js.map +1 -0
  72. package/dist/actions/paneActions.d.ts +3 -40
  73. package/dist/actions/paneActions.d.ts.map +1 -1
  74. package/dist/actions/paneActions.js +4 -1038
  75. package/dist/actions/paneActions.js.map +1 -1
  76. package/dist/actions/types.d.ts +1 -0
  77. package/dist/actions/types.d.ts.map +1 -1
  78. package/dist/actions/types.js.map +1 -1
  79. package/dist/components/{ActionChoiceDialog.d.ts → dialogs/ActionChoiceDialog.d.ts} +1 -1
  80. package/dist/components/dialogs/ActionChoiceDialog.d.ts.map +1 -0
  81. package/dist/components/dialogs/ActionChoiceDialog.js.map +1 -0
  82. package/dist/components/dialogs/ActionConfirmDialog.d.ts.map +1 -0
  83. package/dist/components/dialogs/ActionConfirmDialog.js.map +1 -0
  84. package/dist/components/dialogs/ActionInputDialog.d.ts.map +1 -0
  85. package/dist/components/{ActionInputDialog.js → dialogs/ActionInputDialog.js} +2 -2
  86. package/dist/components/dialogs/ActionInputDialog.js.map +1 -0
  87. package/dist/components/dialogs/ActionProgressDialog.d.ts.map +1 -0
  88. package/dist/components/dialogs/ActionProgressDialog.js.map +1 -0
  89. package/dist/components/dialogs/AgentChoiceDialog.d.ts.map +1 -0
  90. package/dist/components/dialogs/AgentChoiceDialog.js.map +1 -0
  91. package/dist/components/{CloseOptionsDialog.d.ts → dialogs/CloseOptionsDialog.d.ts} +1 -1
  92. package/dist/components/dialogs/CloseOptionsDialog.d.ts.map +1 -0
  93. package/dist/components/dialogs/CloseOptionsDialog.js.map +1 -0
  94. package/dist/components/dialogs/CommandPromptDialog.d.ts.map +1 -0
  95. package/dist/components/{CommandPromptDialog.js → dialogs/CommandPromptDialog.js} +1 -1
  96. package/dist/components/dialogs/CommandPromptDialog.js.map +1 -0
  97. package/dist/components/dialogs/DialogBox.d.ts.map +1 -0
  98. package/dist/components/dialogs/DialogBox.js.map +1 -0
  99. package/dist/components/dialogs/HooksDialog.d.ts.map +1 -0
  100. package/dist/components/dialogs/HooksDialog.js.map +1 -0
  101. package/dist/components/{MergeConfirmationDialog.d.ts → dialogs/MergeConfirmationDialog.d.ts} +1 -1
  102. package/dist/components/dialogs/MergeConfirmationDialog.d.ts.map +1 -0
  103. package/dist/components/dialogs/MergeConfirmationDialog.js.map +1 -0
  104. package/dist/components/{SettingsDialog.d.ts → dialogs/SettingsDialog.d.ts} +1 -1
  105. package/dist/components/dialogs/SettingsDialog.d.ts.map +1 -0
  106. package/dist/components/dialogs/SettingsDialog.js.map +1 -0
  107. package/dist/components/dialogs/UpdateDialog.d.ts.map +1 -0
  108. package/dist/components/dialogs/UpdateDialog.js.map +1 -0
  109. package/dist/components/indicators/CreatingIndicator.d.ts.map +1 -0
  110. package/dist/components/indicators/CreatingIndicator.js.map +1 -0
  111. package/dist/components/indicators/LoadingIndicator.d.ts.map +1 -0
  112. package/dist/components/indicators/LoadingIndicator.js.map +1 -0
  113. package/dist/components/indicators/RunningIndicator.d.ts.map +1 -0
  114. package/dist/components/indicators/RunningIndicator.js.map +1 -0
  115. package/dist/components/indicators/Spinner.d.ts.map +1 -0
  116. package/dist/components/indicators/Spinner.js.map +1 -0
  117. package/dist/components/indicators/UpdatingIndicator.d.ts.map +1 -0
  118. package/dist/components/indicators/UpdatingIndicator.js.map +1 -0
  119. package/dist/components/indicators/index.d.ts +6 -0
  120. package/dist/components/indicators/index.d.ts.map +1 -0
  121. package/dist/components/indicators/index.js +6 -0
  122. package/dist/components/indicators/index.js.map +1 -0
  123. package/dist/components/inputs/CleanTextInput.d.ts.map +1 -0
  124. package/dist/{CleanTextInput.js → components/inputs/CleanTextInput.js} +2 -144
  125. package/dist/components/inputs/CleanTextInput.js.map +1 -0
  126. package/dist/components/inputs/StyledTextInput.d.ts.map +1 -0
  127. package/dist/components/inputs/StyledTextInput.js.map +1 -0
  128. package/dist/components/inputs/index.d.ts +3 -0
  129. package/dist/components/inputs/index.d.ts.map +1 -0
  130. package/dist/components/inputs/index.js +3 -0
  131. package/dist/components/inputs/index.js.map +1 -0
  132. package/dist/components/{KebabMenu.d.ts → panes/KebabMenu.d.ts} +1 -1
  133. package/dist/components/panes/KebabMenu.d.ts.map +1 -0
  134. package/dist/components/panes/KebabMenu.js.map +1 -0
  135. package/dist/components/panes/MergePane.d.ts.map +1 -0
  136. package/dist/{MergePane.js → components/panes/MergePane.js} +1 -1
  137. package/dist/components/panes/MergePane.js.map +1 -0
  138. package/dist/components/{PaneCard.d.ts → panes/PaneCard.d.ts} +1 -1
  139. package/dist/components/panes/PaneCard.d.ts.map +1 -0
  140. package/dist/components/{PaneCard.js → panes/PaneCard.js} +1 -1
  141. package/dist/components/panes/PaneCard.js.map +1 -0
  142. package/dist/components/{PanesGrid.d.ts → panes/PanesGrid.d.ts} +2 -2
  143. package/dist/components/panes/PanesGrid.d.ts.map +1 -0
  144. package/dist/components/{PanesGrid.js → panes/PanesGrid.js} +1 -1
  145. package/dist/components/panes/PanesGrid.js.map +1 -0
  146. package/dist/components/panes/index.d.ts +5 -0
  147. package/dist/components/panes/index.d.ts.map +1 -0
  148. package/dist/components/panes/index.js +5 -0
  149. package/dist/components/panes/index.js.map +1 -0
  150. package/dist/components/popups/agentChoicePopup.d.ts.map +1 -0
  151. package/dist/{popups → components/popups}/agentChoicePopup.js +1 -1
  152. package/dist/components/popups/agentChoicePopup.js.map +1 -0
  153. package/dist/components/popups/choicePopup.d.ts.map +1 -0
  154. package/dist/{popups → components/popups}/choicePopup.js +3 -3
  155. package/dist/components/popups/choicePopup.js.map +1 -0
  156. package/dist/components/popups/config.d.ts.map +1 -0
  157. package/dist/{popups → components/popups}/config.js +1 -1
  158. package/dist/components/popups/config.js.map +1 -0
  159. package/dist/components/popups/confirmPopup.d.ts.map +1 -0
  160. package/dist/{popups → components/popups}/confirmPopup.js +2 -3
  161. package/dist/components/popups/confirmPopup.js.map +1 -0
  162. package/dist/components/popups/hooksPopup.d.ts.map +1 -0
  163. package/dist/{popups → components/popups}/hooksPopup.js +1 -1
  164. package/dist/components/popups/hooksPopup.js.map +1 -0
  165. package/dist/components/popups/inputPopup.d.ts.map +1 -0
  166. package/dist/{popups → components/popups}/inputPopup.js +3 -4
  167. package/dist/components/popups/inputPopup.js.map +1 -0
  168. package/dist/components/popups/kebabMenuPopup.d.ts.map +1 -0
  169. package/dist/{popups → components/popups}/kebabMenuPopup.js +1 -1
  170. package/dist/components/popups/kebabMenuPopup.js.map +1 -0
  171. package/dist/components/popups/logsPopup.d.ts.map +1 -0
  172. package/dist/{popups → components/popups}/logsPopup.js +15 -7
  173. package/dist/components/popups/logsPopup.js.map +1 -0
  174. package/dist/components/popups/mergePopup.d.ts.map +1 -0
  175. package/dist/{popups → components/popups}/mergePopup.js +6 -6
  176. package/dist/components/popups/mergePopup.js.map +1 -0
  177. package/dist/components/popups/newPanePopup.d.ts.map +1 -0
  178. package/dist/{popups → components/popups}/newPanePopup.js +3 -3
  179. package/dist/components/popups/newPanePopup.js.map +1 -0
  180. package/dist/components/popups/progressPopup.d.ts.map +1 -0
  181. package/dist/{popups → components/popups}/progressPopup.js +1 -2
  182. package/dist/components/popups/progressPopup.js.map +1 -0
  183. package/dist/components/popups/remotePopup.d.ts.map +1 -0
  184. package/dist/{popups → components/popups}/remotePopup.js +1 -1
  185. package/dist/components/popups/remotePopup.js.map +1 -0
  186. package/dist/components/popups/settingsPopup.d.ts.map +1 -0
  187. package/dist/{popups → components/popups}/settingsPopup.js +1 -1
  188. package/dist/components/popups/settingsPopup.js.map +1 -0
  189. package/dist/components/popups/shared/FileList.d.ts.map +1 -0
  190. package/dist/components/popups/shared/FileList.js.map +1 -0
  191. package/dist/components/popups/shared/PopupContainer.d.ts.map +1 -0
  192. package/dist/components/popups/shared/PopupContainer.js.map +1 -0
  193. package/dist/components/popups/shared/PopupInputBox.d.ts.map +1 -0
  194. package/dist/components/popups/shared/PopupInputBox.js.map +1 -0
  195. package/dist/components/popups/shared/PopupWrapper.d.ts.map +1 -0
  196. package/dist/components/popups/shared/PopupWrapper.js.map +1 -0
  197. package/dist/components/popups/shared/index.d.ts.map +1 -0
  198. package/dist/components/popups/shared/index.js.map +1 -0
  199. package/dist/components/popups/shortcutsPopup.d.ts.map +1 -0
  200. package/dist/{popups → components/popups}/shortcutsPopup.js +1 -1
  201. package/dist/components/popups/shortcutsPopup.js.map +1 -0
  202. package/dist/components/popups/templates/SimpleInputPopup.d.ts.map +1 -0
  203. package/dist/{popups → components/popups}/templates/SimpleInputPopup.js +2 -2
  204. package/dist/components/popups/templates/SimpleInputPopup.js.map +1 -0
  205. package/dist/components/ui/FileCopyPrompt.d.ts.map +1 -0
  206. package/dist/components/ui/FileCopyPrompt.js.map +1 -0
  207. package/dist/components/{FooterHelp.d.ts → ui/FooterHelp.d.ts} +4 -0
  208. package/dist/components/ui/FooterHelp.d.ts.map +1 -0
  209. package/dist/components/{FooterHelp.js → ui/FooterHelp.js} +45 -1
  210. package/dist/components/ui/FooterHelp.js.map +1 -0
  211. package/dist/components/ui/QRCode.d.ts.map +1 -0
  212. package/dist/components/ui/QRCode.js.map +1 -0
  213. package/dist/components/ui/ToastNotification.d.ts +13 -0
  214. package/dist/components/ui/ToastNotification.d.ts.map +1 -0
  215. package/dist/components/ui/ToastNotification.js +22 -0
  216. package/dist/components/ui/ToastNotification.js.map +1 -0
  217. package/dist/components/ui/index.d.ts +4 -0
  218. package/dist/components/ui/index.d.ts.map +1 -0
  219. package/dist/components/ui/index.js +4 -0
  220. package/dist/components/ui/index.js.map +1 -0
  221. package/dist/constants/timing.d.ts +22 -0
  222. package/dist/constants/timing.d.ts.map +1 -0
  223. package/dist/constants/timing.js +26 -0
  224. package/dist/constants/timing.js.map +1 -0
  225. package/dist/dashboard.js +2 -2
  226. package/dist/hooks/useActionSystem.d.ts +5 -4
  227. package/dist/hooks/useActionSystem.d.ts.map +1 -1
  228. package/dist/hooks/useActionSystem.js +105 -114
  229. package/dist/hooks/useActionSystem.js.map +1 -1
  230. package/dist/hooks/useDebugInfo.d.ts +11 -0
  231. package/dist/hooks/useDebugInfo.d.ts.map +1 -0
  232. package/dist/hooks/useDebugInfo.js +34 -0
  233. package/dist/hooks/useDebugInfo.js.map +1 -0
  234. package/dist/hooks/useDialogState.d.ts +22 -0
  235. package/dist/hooks/useDialogState.d.ts.map +1 -0
  236. package/dist/hooks/useDialogState.js +62 -0
  237. package/dist/hooks/useDialogState.js.map +1 -0
  238. package/dist/hooks/useInputHandling.d.ts +60 -0
  239. package/dist/hooks/useInputHandling.d.ts.map +1 -0
  240. package/dist/hooks/useInputHandling.js +294 -0
  241. package/dist/hooks/useInputHandling.js.map +1 -0
  242. package/dist/hooks/useLayoutManagement.d.ts +12 -0
  243. package/dist/hooks/useLayoutManagement.d.ts.map +1 -0
  244. package/dist/hooks/useLayoutManagement.js +63 -0
  245. package/dist/hooks/useLayoutManagement.js.map +1 -0
  246. package/dist/hooks/usePaneCreation.d.ts.map +1 -1
  247. package/dist/hooks/usePaneCreation.js +4 -9
  248. package/dist/hooks/usePaneCreation.js.map +1 -1
  249. package/dist/hooks/usePaneLoading.d.ts +45 -0
  250. package/dist/hooks/usePaneLoading.d.ts.map +1 -0
  251. package/dist/hooks/usePaneLoading.js +208 -0
  252. package/dist/hooks/usePaneLoading.js.map +1 -0
  253. package/dist/hooks/usePaneRunner.d.ts.map +1 -1
  254. package/dist/hooks/usePaneRunner.js +9 -6
  255. package/dist/hooks/usePaneRunner.js.map +1 -1
  256. package/dist/hooks/usePaneSync.d.ts +34 -0
  257. package/dist/hooks/usePaneSync.d.ts.map +1 -0
  258. package/dist/hooks/usePaneSync.js +186 -0
  259. package/dist/hooks/usePaneSync.js.map +1 -0
  260. package/dist/hooks/usePanes.d.ts.map +1 -1
  261. package/dist/hooks/usePanes.js +68 -406
  262. package/dist/hooks/usePanes.js.map +1 -1
  263. package/dist/hooks/useServices.d.ts +24 -0
  264. package/dist/hooks/useServices.d.ts.map +1 -0
  265. package/dist/hooks/useServices.js +39 -0
  266. package/dist/hooks/useServices.js.map +1 -0
  267. package/dist/hooks/useShellDetection.d.ts +10 -0
  268. package/dist/hooks/useShellDetection.d.ts.map +1 -0
  269. package/dist/hooks/useShellDetection.js +67 -0
  270. package/dist/hooks/useShellDetection.js.map +1 -0
  271. package/dist/hooks/useStatusMessages.d.ts +11 -0
  272. package/dist/hooks/useStatusMessages.d.ts.map +1 -0
  273. package/dist/hooks/useStatusMessages.js +32 -0
  274. package/dist/hooks/useStatusMessages.js.map +1 -0
  275. package/dist/hooks/useTemporaryStatus.d.ts +13 -0
  276. package/dist/hooks/useTemporaryStatus.d.ts.map +1 -0
  277. package/dist/hooks/useTemporaryStatus.js +30 -0
  278. package/dist/hooks/useTemporaryStatus.js.map +1 -0
  279. package/dist/hooks/useTerminalWidth.d.ts.map +1 -1
  280. package/dist/hooks/useTerminalWidth.js +7 -12
  281. package/dist/hooks/useTerminalWidth.js.map +1 -1
  282. package/dist/hooks/useTunnelManagement.d.ts +18 -0
  283. package/dist/hooks/useTunnelManagement.d.ts.map +1 -0
  284. package/dist/hooks/useTunnelManagement.js +55 -0
  285. package/dist/hooks/useTunnelManagement.js.map +1 -0
  286. package/dist/hooks/useWorktreeActions.d.ts.map +1 -1
  287. package/dist/hooks/useWorktreeActions.js +18 -13
  288. package/dist/hooks/useWorktreeActions.js.map +1 -1
  289. package/dist/index.js +124 -94
  290. package/dist/index.js.map +1 -1
  291. package/dist/layout/LayoutCalculator.d.ts +62 -0
  292. package/dist/layout/LayoutCalculator.d.ts.map +1 -0
  293. package/dist/layout/LayoutCalculator.js +140 -0
  294. package/dist/layout/LayoutCalculator.js.map +1 -0
  295. package/dist/layout/SpacerManager.d.ts +64 -0
  296. package/dist/layout/SpacerManager.d.ts.map +1 -0
  297. package/dist/layout/SpacerManager.js +156 -0
  298. package/dist/layout/SpacerManager.js.map +1 -0
  299. package/dist/layout/TmuxLayoutApplier.d.ts +62 -0
  300. package/dist/layout/TmuxLayoutApplier.d.ts.map +1 -0
  301. package/dist/layout/TmuxLayoutApplier.js +147 -0
  302. package/dist/layout/TmuxLayoutApplier.js.map +1 -0
  303. package/dist/panes/decorative-pane.d.ts.map +1 -0
  304. package/dist/{decorative-pane.js → panes/decorative-pane.js} +1 -1
  305. package/dist/panes/decorative-pane.js.map +1 -0
  306. package/dist/panes/spacer-pane.d.ts.map +1 -0
  307. package/dist/panes/spacer-pane.js.map +1 -0
  308. package/dist/server/embedded-assets.d.ts.map +1 -1
  309. package/dist/server/embedded-assets.js +727 -5677
  310. package/dist/server/embedded-assets.js.map +1 -1
  311. package/dist/server/routes/actionsRoutes.d.ts +2 -0
  312. package/dist/server/routes/actionsRoutes.d.ts.map +1 -0
  313. package/dist/server/routes/actionsRoutes.js +110 -0
  314. package/dist/server/routes/actionsRoutes.js.map +1 -0
  315. package/dist/server/routes/healthRoutes.d.ts +13 -0
  316. package/dist/server/routes/healthRoutes.d.ts.map +1 -0
  317. package/dist/server/routes/healthRoutes.js +70 -0
  318. package/dist/server/routes/healthRoutes.js.map +1 -0
  319. package/dist/server/routes/index.d.ts +8 -0
  320. package/dist/server/routes/index.d.ts.map +1 -0
  321. package/dist/server/routes/index.js +67 -0
  322. package/dist/server/routes/index.js.map +1 -0
  323. package/dist/server/routes/keysRoutes.d.ts +33 -0
  324. package/dist/server/routes/keysRoutes.d.ts.map +1 -0
  325. package/dist/server/routes/keysRoutes.js +128 -0
  326. package/dist/server/routes/keysRoutes.js.map +1 -0
  327. package/dist/server/routes/panesRoutes.d.ts +2 -0
  328. package/dist/server/routes/panesRoutes.d.ts.map +1 -0
  329. package/dist/server/routes/panesRoutes.js +373 -0
  330. package/dist/server/routes/panesRoutes.js.map +1 -0
  331. package/dist/server/routes/settingsRoutes.d.ts +94 -0
  332. package/dist/server/routes/settingsRoutes.d.ts.map +1 -0
  333. package/dist/server/routes/settingsRoutes.js +159 -0
  334. package/dist/server/routes/settingsRoutes.js.map +1 -0
  335. package/dist/server/routes/streamRoutes.d.ts +18 -0
  336. package/dist/server/routes/streamRoutes.d.ts.map +1 -0
  337. package/dist/server/routes/streamRoutes.js +87 -0
  338. package/dist/server/routes/streamRoutes.js.map +1 -0
  339. package/dist/server/routes/tunnelRoutes.d.ts +11 -0
  340. package/dist/server/routes/tunnelRoutes.d.ts.map +1 -0
  341. package/dist/server/routes/tunnelRoutes.js +28 -0
  342. package/dist/server/routes/tunnelRoutes.js.map +1 -0
  343. package/dist/server/routes.d.ts +16 -2
  344. package/dist/server/routes.d.ts.map +1 -1
  345. package/dist/server/routes.js +16 -878
  346. package/dist/server/routes.js.map +1 -1
  347. package/dist/{AutoUpdater.d.ts → services/AutoUpdater.d.ts} +1 -0
  348. package/dist/services/AutoUpdater.d.ts.map +1 -0
  349. package/dist/{AutoUpdater.js → services/AutoUpdater.js} +44 -10
  350. package/dist/services/AutoUpdater.js.map +1 -0
  351. package/dist/services/PaneAnalyzer.d.ts.map +1 -0
  352. package/dist/{PaneAnalyzer.js → services/PaneAnalyzer.js} +1 -1
  353. package/dist/services/PaneAnalyzer.js.map +1 -0
  354. package/dist/services/PaneLifecycleManager.d.ts +60 -0
  355. package/dist/services/PaneLifecycleManager.d.ts.map +1 -0
  356. package/dist/services/PaneLifecycleManager.js +119 -0
  357. package/dist/services/PaneLifecycleManager.js.map +1 -0
  358. package/dist/services/PaneWorkerManager.d.ts.map +1 -1
  359. package/dist/services/PaneWorkerManager.js +6 -3
  360. package/dist/services/PaneWorkerManager.js.map +1 -1
  361. package/dist/services/PopupManager.d.ts +68 -0
  362. package/dist/services/PopupManager.d.ts.map +1 -0
  363. package/dist/services/PopupManager.js +415 -0
  364. package/dist/services/PopupManager.js.map +1 -0
  365. package/dist/services/StatusDetector.d.ts.map +1 -1
  366. package/dist/services/StatusDetector.js +6 -8
  367. package/dist/services/StatusDetector.js.map +1 -1
  368. package/dist/services/TerminalStreamer.d.ts +1 -0
  369. package/dist/services/TerminalStreamer.d.ts.map +1 -1
  370. package/dist/services/TerminalStreamer.js +22 -16
  371. package/dist/services/TerminalStreamer.js.map +1 -1
  372. package/dist/services/TmuxService.d.ts +346 -0
  373. package/dist/services/TmuxService.d.ts.map +1 -0
  374. package/dist/services/TmuxService.js +849 -0
  375. package/dist/services/TmuxService.js.map +1 -0
  376. package/dist/services/ToastService.d.ts +71 -0
  377. package/dist/services/ToastService.d.ts.map +1 -0
  378. package/dist/services/ToastService.js +151 -0
  379. package/dist/services/ToastService.js.map +1 -0
  380. package/dist/services/TunnelService.d.ts.map +1 -1
  381. package/dist/services/TunnelService.js +2 -1
  382. package/dist/services/TunnelService.js.map +1 -1
  383. package/dist/shared/StateManager.d.ts +22 -0
  384. package/dist/shared/StateManager.d.ts.map +1 -1
  385. package/dist/shared/StateManager.js +68 -1
  386. package/dist/shared/StateManager.js.map +1 -1
  387. package/dist/terminal.js +3 -3
  388. package/dist/types.d.ts +7 -0
  389. package/dist/types.d.ts.map +1 -1
  390. package/dist/utils/asciiArt.d.ts.map +1 -1
  391. package/dist/utils/asciiArt.js +9 -22
  392. package/dist/utils/asciiArt.js.map +1 -1
  393. package/dist/utils/atomicWrite.d.ts +33 -0
  394. package/dist/utils/atomicWrite.d.ts.map +1 -0
  395. package/dist/utils/atomicWrite.js +89 -0
  396. package/dist/utils/atomicWrite.js.map +1 -0
  397. package/dist/utils/conflictMonitor.d.ts +20 -0
  398. package/dist/utils/conflictMonitor.d.ts.map +1 -0
  399. package/dist/utils/conflictMonitor.js +113 -0
  400. package/dist/utils/conflictMonitor.js.map +1 -0
  401. package/dist/utils/conflictResolutionPane.d.ts.map +1 -1
  402. package/dist/utils/conflictResolutionPane.js +70 -79
  403. package/dist/utils/conflictResolutionPane.js.map +1 -1
  404. package/dist/utils/errorHandling.d.ts +37 -0
  405. package/dist/utils/errorHandling.d.ts.map +1 -0
  406. package/dist/utils/errorHandling.js +138 -0
  407. package/dist/utils/errorHandling.js.map +1 -0
  408. package/dist/utils/generated-agents-doc.d.ts +1 -1
  409. package/dist/utils/generated-agents-doc.js +1 -1
  410. package/dist/utils/git.d.ts +4 -0
  411. package/dist/utils/git.d.ts.map +1 -1
  412. package/dist/utils/git.js +15 -0
  413. package/dist/utils/git.js.map +1 -1
  414. package/dist/utils/hooksDocs.d.ts +1 -1
  415. package/dist/utils/layoutManager.d.ts +20 -16
  416. package/dist/utils/layoutManager.d.ts.map +1 -1
  417. package/dist/utils/layoutManager.js +159 -380
  418. package/dist/utils/layoutManager.js.map +1 -1
  419. package/dist/utils/mergeExecution.d.ts.map +1 -1
  420. package/dist/utils/mergeExecution.js +8 -1
  421. package/dist/utils/mergeExecution.js.map +1 -1
  422. package/dist/utils/mergeValidation.d.ts.map +1 -1
  423. package/dist/utils/mergeValidation.js +46 -13
  424. package/dist/utils/mergeValidation.js.map +1 -1
  425. package/dist/utils/paneCreation.d.ts.map +1 -1
  426. package/dist/utils/paneCreation.js +115 -106
  427. package/dist/utils/paneCreation.js.map +1 -1
  428. package/dist/utils/paneRebinding.d.ts +14 -0
  429. package/dist/utils/paneRebinding.d.ts.map +1 -0
  430. package/dist/utils/paneRebinding.js +30 -0
  431. package/dist/utils/paneRebinding.js.map +1 -0
  432. package/dist/utils/popup.d.ts.map +1 -1
  433. package/dist/utils/popup.js +11 -17
  434. package/dist/utils/popup.js.map +1 -1
  435. package/dist/utils/postPaneCleanup.d.ts.map +1 -1
  436. package/dist/utils/postPaneCleanup.js +5 -14
  437. package/dist/utils/postPaneCleanup.js.map +1 -1
  438. package/dist/utils/shellPaneDetection.d.ts +3 -3
  439. package/dist/utils/shellPaneDetection.d.ts.map +1 -1
  440. package/dist/utils/shellPaneDetection.js +43 -29
  441. package/dist/utils/shellPaneDetection.js.map +1 -1
  442. package/dist/utils/systemCheck.d.ts +19 -0
  443. package/dist/utils/systemCheck.d.ts.map +1 -0
  444. package/dist/utils/systemCheck.js +160 -0
  445. package/dist/utils/systemCheck.js.map +1 -0
  446. package/dist/utils/tmux.d.ts +26 -7
  447. package/dist/utils/tmux.d.ts.map +1 -1
  448. package/dist/utils/tmux.js +84 -76
  449. package/dist/utils/tmux.js.map +1 -1
  450. package/dist/utils/welcomePane.d.ts +2 -2
  451. package/dist/utils/welcomePane.d.ts.map +1 -1
  452. package/dist/utils/welcomePane.js +19 -33
  453. package/dist/utils/welcomePane.js.map +1 -1
  454. package/dist/utils/welcomePaneManager.d.ts.map +1 -1
  455. package/dist/utils/welcomePaneManager.js +11 -25
  456. package/dist/utils/welcomePaneManager.js.map +1 -1
  457. package/dist/workers/PaneWorker.js +7 -9
  458. package/dist/workers/PaneWorker.js.map +1 -1
  459. package/dist/workers/updateChecker.js +1 -1
  460. package/dist/workers/updateChecker.js.map +1 -1
  461. package/package.json +3 -1
  462. package/dist/AutoUpdater.d.ts.map +0 -1
  463. package/dist/AutoUpdater.js.map +0 -1
  464. package/dist/BetterTextInput.d.ts +0 -10
  465. package/dist/BetterTextInput.d.ts.map +0 -1
  466. package/dist/BetterTextInput.js +0 -177
  467. package/dist/BetterTextInput.js.map +0 -1
  468. package/dist/CleanTextInput.d.ts.map +0 -1
  469. package/dist/CleanTextInput.js.map +0 -1
  470. package/dist/EnhancedTextInput.d.ts +0 -13
  471. package/dist/EnhancedTextInput.d.ts.map +0 -1
  472. package/dist/EnhancedTextInput.js +0 -443
  473. package/dist/EnhancedTextInput.js.map +0 -1
  474. package/dist/GeminiTextInput.d.ts +0 -12
  475. package/dist/GeminiTextInput.d.ts.map +0 -1
  476. package/dist/GeminiTextInput.js +0 -210
  477. package/dist/GeminiTextInput.js.map +0 -1
  478. package/dist/MergePane.d.ts.map +0 -1
  479. package/dist/MergePane.js.map +0 -1
  480. package/dist/MultilineTextInput.d.ts +0 -10
  481. package/dist/MultilineTextInput.d.ts.map +0 -1
  482. package/dist/MultilineTextInput.js +0 -184
  483. package/dist/MultilineTextInput.js.map +0 -1
  484. package/dist/PaneAnalyzer.d.ts.map +0 -1
  485. package/dist/PaneAnalyzer.js.map +0 -1
  486. package/dist/SimpleEnhancedInput.d.ts +0 -13
  487. package/dist/SimpleEnhancedInput.d.ts.map +0 -1
  488. package/dist/SimpleEnhancedInput.js +0 -639
  489. package/dist/SimpleEnhancedInput.js.map +0 -1
  490. package/dist/SimpleGeminiInput.d.ts +0 -12
  491. package/dist/SimpleGeminiInput.d.ts.map +0 -1
  492. package/dist/SimpleGeminiInput.js +0 -223
  493. package/dist/SimpleGeminiInput.js.map +0 -1
  494. package/dist/StyledTextInput.d.ts.map +0 -1
  495. package/dist/StyledTextInput.js.map +0 -1
  496. package/dist/components/ActionChoiceDialog.d.ts.map +0 -1
  497. package/dist/components/ActionChoiceDialog.js.map +0 -1
  498. package/dist/components/ActionConfirmDialog.d.ts.map +0 -1
  499. package/dist/components/ActionConfirmDialog.js.map +0 -1
  500. package/dist/components/ActionInputDialog.d.ts.map +0 -1
  501. package/dist/components/ActionInputDialog.js.map +0 -1
  502. package/dist/components/ActionProgressDialog.d.ts.map +0 -1
  503. package/dist/components/ActionProgressDialog.js.map +0 -1
  504. package/dist/components/AgentChoiceDialog.d.ts.map +0 -1
  505. package/dist/components/AgentChoiceDialog.js.map +0 -1
  506. package/dist/components/CloseOptionsDialog.d.ts.map +0 -1
  507. package/dist/components/CloseOptionsDialog.js.map +0 -1
  508. package/dist/components/CommandPromptDialog.d.ts.map +0 -1
  509. package/dist/components/CommandPromptDialog.js.map +0 -1
  510. package/dist/components/CreatingIndicator.d.ts.map +0 -1
  511. package/dist/components/CreatingIndicator.js.map +0 -1
  512. package/dist/components/DialogBox.d.ts.map +0 -1
  513. package/dist/components/DialogBox.js.map +0 -1
  514. package/dist/components/FileCopyPrompt.d.ts.map +0 -1
  515. package/dist/components/FileCopyPrompt.js.map +0 -1
  516. package/dist/components/FooterHelp.d.ts.map +0 -1
  517. package/dist/components/FooterHelp.js.map +0 -1
  518. package/dist/components/HooksDialog.d.ts.map +0 -1
  519. package/dist/components/HooksDialog.js.map +0 -1
  520. package/dist/components/KebabMenu.d.ts.map +0 -1
  521. package/dist/components/KebabMenu.js.map +0 -1
  522. package/dist/components/LoadingIndicator.d.ts.map +0 -1
  523. package/dist/components/LoadingIndicator.js.map +0 -1
  524. package/dist/components/MergeConfirmationDialog.d.ts.map +0 -1
  525. package/dist/components/MergeConfirmationDialog.js.map +0 -1
  526. package/dist/components/PaneCard.d.ts.map +0 -1
  527. package/dist/components/PaneCard.js.map +0 -1
  528. package/dist/components/PanesGrid.d.ts.map +0 -1
  529. package/dist/components/PanesGrid.js.map +0 -1
  530. package/dist/components/QRCode.d.ts.map +0 -1
  531. package/dist/components/QRCode.js.map +0 -1
  532. package/dist/components/RunningIndicator.d.ts.map +0 -1
  533. package/dist/components/RunningIndicator.js.map +0 -1
  534. package/dist/components/SettingsDialog.d.ts.map +0 -1
  535. package/dist/components/SettingsDialog.js.map +0 -1
  536. package/dist/components/Spinner.d.ts.map +0 -1
  537. package/dist/components/Spinner.js.map +0 -1
  538. package/dist/components/UpdateDialog.d.ts.map +0 -1
  539. package/dist/components/UpdateDialog.js.map +0 -1
  540. package/dist/components/UpdatingIndicator.d.ts.map +0 -1
  541. package/dist/components/UpdatingIndicator.js.map +0 -1
  542. package/dist/decorative-pane.d.ts.map +0 -1
  543. package/dist/decorative-pane.js.map +0 -1
  544. package/dist/popups/agentChoicePopup.d.ts.map +0 -1
  545. package/dist/popups/agentChoicePopup.js.map +0 -1
  546. package/dist/popups/choicePopup.d.ts.map +0 -1
  547. package/dist/popups/choicePopup.js.map +0 -1
  548. package/dist/popups/components/FileList.d.ts.map +0 -1
  549. package/dist/popups/components/FileList.js.map +0 -1
  550. package/dist/popups/components/PopupContainer.d.ts.map +0 -1
  551. package/dist/popups/components/PopupContainer.js.map +0 -1
  552. package/dist/popups/components/PopupInputBox.d.ts.map +0 -1
  553. package/dist/popups/components/PopupInputBox.js.map +0 -1
  554. package/dist/popups/components/PopupWrapper.d.ts.map +0 -1
  555. package/dist/popups/components/PopupWrapper.js.map +0 -1
  556. package/dist/popups/components/index.d.ts.map +0 -1
  557. package/dist/popups/components/index.js.map +0 -1
  558. package/dist/popups/config.d.ts.map +0 -1
  559. package/dist/popups/config.js.map +0 -1
  560. package/dist/popups/confirmPopup.d.ts.map +0 -1
  561. package/dist/popups/confirmPopup.js.map +0 -1
  562. package/dist/popups/hooksPopup.d.ts.map +0 -1
  563. package/dist/popups/hooksPopup.js.map +0 -1
  564. package/dist/popups/inputPopup.d.ts.map +0 -1
  565. package/dist/popups/inputPopup.js.map +0 -1
  566. package/dist/popups/kebabMenuPopup.d.ts.map +0 -1
  567. package/dist/popups/kebabMenuPopup.js.map +0 -1
  568. package/dist/popups/logsPopup.d.ts.map +0 -1
  569. package/dist/popups/logsPopup.js.map +0 -1
  570. package/dist/popups/mergePopup.d.ts.map +0 -1
  571. package/dist/popups/mergePopup.js.map +0 -1
  572. package/dist/popups/newPanePopup.d.ts.map +0 -1
  573. package/dist/popups/newPanePopup.js.map +0 -1
  574. package/dist/popups/progressPopup.d.ts.map +0 -1
  575. package/dist/popups/progressPopup.js.map +0 -1
  576. package/dist/popups/remotePopup.d.ts.map +0 -1
  577. package/dist/popups/remotePopup.js.map +0 -1
  578. package/dist/popups/settingsPopup.d.ts.map +0 -1
  579. package/dist/popups/settingsPopup.js.map +0 -1
  580. package/dist/popups/shortcutsPopup.d.ts.map +0 -1
  581. package/dist/popups/shortcutsPopup.js.map +0 -1
  582. package/dist/popups/templates/SimpleInputPopup.d.ts.map +0 -1
  583. package/dist/popups/templates/SimpleInputPopup.js.map +0 -1
  584. package/dist/server/static.d.ts +0 -6
  585. package/dist/server/static.d.ts.map +0 -1
  586. package/dist/server/static.js +0 -3040
  587. package/dist/server/static.js.map +0 -1
  588. package/dist/spacer-pane.d.ts.map +0 -1
  589. package/dist/spacer-pane.js.map +0 -1
  590. /package/dist/components/{ActionChoiceDialog.js → dialogs/ActionChoiceDialog.js} +0 -0
  591. /package/dist/components/{ActionConfirmDialog.d.ts → dialogs/ActionConfirmDialog.d.ts} +0 -0
  592. /package/dist/components/{ActionConfirmDialog.js → dialogs/ActionConfirmDialog.js} +0 -0
  593. /package/dist/components/{ActionInputDialog.d.ts → dialogs/ActionInputDialog.d.ts} +0 -0
  594. /package/dist/components/{ActionProgressDialog.d.ts → dialogs/ActionProgressDialog.d.ts} +0 -0
  595. /package/dist/components/{ActionProgressDialog.js → dialogs/ActionProgressDialog.js} +0 -0
  596. /package/dist/components/{AgentChoiceDialog.d.ts → dialogs/AgentChoiceDialog.d.ts} +0 -0
  597. /package/dist/components/{AgentChoiceDialog.js → dialogs/AgentChoiceDialog.js} +0 -0
  598. /package/dist/components/{CloseOptionsDialog.js → dialogs/CloseOptionsDialog.js} +0 -0
  599. /package/dist/components/{CommandPromptDialog.d.ts → dialogs/CommandPromptDialog.d.ts} +0 -0
  600. /package/dist/components/{DialogBox.d.ts → dialogs/DialogBox.d.ts} +0 -0
  601. /package/dist/components/{DialogBox.js → dialogs/DialogBox.js} +0 -0
  602. /package/dist/components/{HooksDialog.d.ts → dialogs/HooksDialog.d.ts} +0 -0
  603. /package/dist/components/{HooksDialog.js → dialogs/HooksDialog.js} +0 -0
  604. /package/dist/components/{MergeConfirmationDialog.js → dialogs/MergeConfirmationDialog.js} +0 -0
  605. /package/dist/components/{SettingsDialog.js → dialogs/SettingsDialog.js} +0 -0
  606. /package/dist/components/{UpdateDialog.d.ts → dialogs/UpdateDialog.d.ts} +0 -0
  607. /package/dist/components/{UpdateDialog.js → dialogs/UpdateDialog.js} +0 -0
  608. /package/dist/components/{CreatingIndicator.d.ts → indicators/CreatingIndicator.d.ts} +0 -0
  609. /package/dist/components/{CreatingIndicator.js → indicators/CreatingIndicator.js} +0 -0
  610. /package/dist/components/{LoadingIndicator.d.ts → indicators/LoadingIndicator.d.ts} +0 -0
  611. /package/dist/components/{LoadingIndicator.js → indicators/LoadingIndicator.js} +0 -0
  612. /package/dist/components/{RunningIndicator.d.ts → indicators/RunningIndicator.d.ts} +0 -0
  613. /package/dist/components/{RunningIndicator.js → indicators/RunningIndicator.js} +0 -0
  614. /package/dist/components/{Spinner.d.ts → indicators/Spinner.d.ts} +0 -0
  615. /package/dist/components/{Spinner.js → indicators/Spinner.js} +0 -0
  616. /package/dist/components/{UpdatingIndicator.d.ts → indicators/UpdatingIndicator.d.ts} +0 -0
  617. /package/dist/components/{UpdatingIndicator.js → indicators/UpdatingIndicator.js} +0 -0
  618. /package/dist/{CleanTextInput.d.ts → components/inputs/CleanTextInput.d.ts} +0 -0
  619. /package/dist/{StyledTextInput.d.ts → components/inputs/StyledTextInput.d.ts} +0 -0
  620. /package/dist/{StyledTextInput.js → components/inputs/StyledTextInput.js} +0 -0
  621. /package/dist/components/{KebabMenu.js → panes/KebabMenu.js} +0 -0
  622. /package/dist/{MergePane.d.ts → components/panes/MergePane.d.ts} +0 -0
  623. /package/dist/{popups → components/popups}/agentChoicePopup.d.ts +0 -0
  624. /package/dist/{popups → components/popups}/choicePopup.d.ts +0 -0
  625. /package/dist/{popups → components/popups}/config.d.ts +0 -0
  626. /package/dist/{popups → components/popups}/confirmPopup.d.ts +0 -0
  627. /package/dist/{popups → components/popups}/hooksPopup.d.ts +0 -0
  628. /package/dist/{popups → components/popups}/inputPopup.d.ts +0 -0
  629. /package/dist/{popups → components/popups}/kebabMenuPopup.d.ts +0 -0
  630. /package/dist/{popups → components/popups}/logsPopup.d.ts +0 -0
  631. /package/dist/{popups → components/popups}/mergePopup.d.ts +0 -0
  632. /package/dist/{popups → components/popups}/newPanePopup.d.ts +0 -0
  633. /package/dist/{popups → components/popups}/progressPopup.d.ts +0 -0
  634. /package/dist/{popups → components/popups}/remotePopup.d.ts +0 -0
  635. /package/dist/{popups → components/popups}/settingsPopup.d.ts +0 -0
  636. /package/dist/{popups/components → components/popups/shared}/FileList.d.ts +0 -0
  637. /package/dist/{popups/components → components/popups/shared}/FileList.js +0 -0
  638. /package/dist/{popups/components → components/popups/shared}/PopupContainer.d.ts +0 -0
  639. /package/dist/{popups/components → components/popups/shared}/PopupContainer.js +0 -0
  640. /package/dist/{popups/components → components/popups/shared}/PopupInputBox.d.ts +0 -0
  641. /package/dist/{popups/components → components/popups/shared}/PopupInputBox.js +0 -0
  642. /package/dist/{popups/components → components/popups/shared}/PopupWrapper.d.ts +0 -0
  643. /package/dist/{popups/components → components/popups/shared}/PopupWrapper.js +0 -0
  644. /package/dist/{popups/components → components/popups/shared}/index.d.ts +0 -0
  645. /package/dist/{popups/components → components/popups/shared}/index.js +0 -0
  646. /package/dist/{popups → components/popups}/shortcutsPopup.d.ts +0 -0
  647. /package/dist/{popups → components/popups}/templates/SimpleInputPopup.d.ts +0 -0
  648. /package/dist/components/{FileCopyPrompt.d.ts → ui/FileCopyPrompt.d.ts} +0 -0
  649. /package/dist/components/{FileCopyPrompt.js → ui/FileCopyPrompt.js} +0 -0
  650. /package/dist/components/{QRCode.d.ts → ui/QRCode.d.ts} +0 -0
  651. /package/dist/components/{QRCode.js → ui/QRCode.js} +0 -0
  652. /package/dist/{decorative-pane.d.ts → panes/decorative-pane.d.ts} +0 -0
  653. /package/dist/{spacer-pane.d.ts → panes/spacer-pane.d.ts} +0 -0
  654. /package/dist/{spacer-pane.js → panes/spacer-pane.js} +0 -0
  655. /package/dist/{PaneAnalyzer.d.ts → services/PaneAnalyzer.d.ts} +0 -0
package/dist/DmuxApp.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { Box, Text, useInput, useApp, useStdout } from "ink";
3
- import { execSync } from "child_process";
4
- import fs from "fs/promises";
5
- import path from "path";
2
+ import { Box, Text, useApp, useStdout } from "ink";
6
3
  import { createRequire } from "module";
4
+ import { TmuxService } from "./services/TmuxService.js";
7
5
  // Hooks
8
6
  import usePanes from "./hooks/usePanes.js";
9
7
  import useProjectSettings from "./hooks/useProjectSettings.js";
@@ -15,38 +13,40 @@ import useAgentStatus from "./hooks/useAgentStatus.js";
15
13
  import usePaneRunner from "./hooks/usePaneRunner.js";
16
14
  import usePaneCreation from "./hooks/usePaneCreation.js";
17
15
  import useActionSystem from "./hooks/useActionSystem.js";
16
+ import { useStatusMessages } from "./hooks/useStatusMessages.js";
17
+ import { useLayoutManagement } from "./hooks/useLayoutManagement.js";
18
+ import { useInputHandling } from "./hooks/useInputHandling.js";
19
+ import { useDialogState } from "./hooks/useDialogState.js";
20
+ import { useTunnelManagement } from "./hooks/useTunnelManagement.js";
21
+ import { useDebugInfo } from "./hooks/useDebugInfo.js";
18
22
  // Utils
19
- import { enforceControlPaneSize } from "./utils/tmux.js";
20
23
  import { SIDEBAR_WIDTH } from "./utils/layoutManager.js";
21
- import { suggestCommand } from "./utils/commands.js";
22
- import { generateSlug } from "./utils/slug.js";
23
- import { getMainBranch } from "./utils/git.js";
24
- import { capturePaneContent } from "./utils/paneCapture.js";
25
- import { supportsPopups, launchNodePopupNonBlocking, POPUP_POSITIONING, } from "./utils/popup.js";
24
+ import { supportsPopups } from "./utils/popup.js";
26
25
  import { StateManager } from "./shared/StateManager.js";
27
- import { LogService } from "./services/LogService.js";
26
+ import { REPAINT_SPINNER_DURATION, } from "./constants/timing.js";
28
27
  import { getStatusDetector, } from "./services/StatusDetector.js";
29
- import { PaneAction, getAvailableActions, } from "./actions/index.js";
30
- import { SettingsManager, SETTING_DEFINITIONS, } from "./utils/settingsManager.js";
28
+ import { SettingsManager } from "./utils/settingsManager.js";
29
+ import { useServices } from "./hooks/useServices.js";
30
+ import { PaneLifecycleManager } from "./services/PaneLifecycleManager.js";
31
31
  import { fileURLToPath } from "url";
32
32
  import { dirname } from "path";
33
33
  const __filename = fileURLToPath(import.meta.url);
34
34
  const __dirname = dirname(__filename);
35
35
  const require = createRequire(import.meta.url);
36
36
  const packageJson = require("../package.json");
37
- import PanesGrid from "./components/PanesGrid.js";
38
- import CommandPromptDialog from "./components/CommandPromptDialog.js";
39
- import FileCopyPrompt from "./components/FileCopyPrompt.js";
40
- import LoadingIndicator from "./components/LoadingIndicator.js";
41
- import RunningIndicator from "./components/RunningIndicator.js";
42
- import UpdatingIndicator from "./components/UpdatingIndicator.js";
43
- import FooterHelp from "./components/FooterHelp.js";
44
- const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoot, autoUpdater, serverPort, server, controlPaneId, }) => {
37
+ import PanesGrid from "./components/panes/PanesGrid.js";
38
+ import CommandPromptDialog from "./components/dialogs/CommandPromptDialog.js";
39
+ import FileCopyPrompt from "./components/ui/FileCopyPrompt.js";
40
+ import LoadingIndicator from "./components/indicators/LoadingIndicator.js";
41
+ import RunningIndicator from "./components/indicators/RunningIndicator.js";
42
+ import UpdatingIndicator from "./components/indicators/UpdatingIndicator.js";
43
+ import FooterHelp from "./components/ui/FooterHelp.js";
44
+ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoot, autoUpdater, serverPort, server, controlPaneId, rerenderRef, }) => {
45
45
  const { stdout } = useStdout();
46
46
  const terminalHeight = stdout?.rows || 40;
47
47
  /* panes state moved to usePanes */
48
48
  const [selectedIndex, setSelectedIndex] = useState(0);
49
- const [statusMessage, setStatusMessage] = useState("");
49
+ const { statusMessage, setStatusMessage, showStatus, clearStatus } = useStatusMessages();
50
50
  const [isCreatingPane, setIsCreatingPane] = useState(false);
51
51
  // Settings state
52
52
  const [settingsManager] = useState(() => new SettingsManager(projectRoot));
@@ -55,18 +55,16 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
55
55
  // Spinner state - shows for a few frames to force render
56
56
  const [showRepaintSpinner, setShowRepaintSpinner] = useState(false);
57
57
  const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
58
- const [showCommandPrompt, setShowCommandPrompt] = useState(null);
59
- const [commandInput, setCommandInput] = useState("");
60
- const [showFileCopyPrompt, setShowFileCopyPrompt] = useState(false);
61
- const [currentCommandType, setCurrentCommandType] = useState(null);
62
- const [runningCommand, setRunningCommand] = useState(false);
63
- const [quitConfirmMode, setQuitConfirmMode] = useState(false);
64
- // Debug message state - for temporary logging messages
65
- const [debugMessage, setDebugMessage] = useState("");
66
- // Current git branch state (for dev builds)
67
- const [currentBranch, setCurrentBranch] = useState(null);
58
+ // Dialog state management
59
+ const dialogState = useDialogState();
60
+ const { showCommandPrompt, setShowCommandPrompt, commandInput, setCommandInput, showFileCopyPrompt, setShowFileCopyPrompt, currentCommandType, setCurrentCommandType, runningCommand, setRunningCommand, quitConfirmMode, setQuitConfirmMode, } = dialogState;
61
+ // Tunnel/network state management
62
+ const tunnelState = useTunnelManagement();
63
+ const { tunnelUrl, setTunnelUrl, tunnelCreating, setTunnelCreating, tunnelCopied, setTunnelCopied, localIp, setLocalIp, } = tunnelState;
64
+ // Debug/development info
65
+ const { debugMessage, setDebugMessage, currentBranch } = useDebugInfo(__dirname);
68
66
  // Update state handled by hook
69
- const { updateInfo, showUpdateDialog, isUpdating, performUpdate, skipUpdate, dismissUpdate, updateAvailable, } = useAutoUpdater(autoUpdater, setStatusMessage);
67
+ const { updateInfo, isUpdating, updateAvailable, } = useAutoUpdater(autoUpdater, setStatusMessage);
70
68
  const { exit } = useApp();
71
69
  // Flag to ignore input temporarily after popup closes (prevents buffered keys)
72
70
  const [ignoreInput, setIgnoreInput] = useState(false);
@@ -80,35 +78,43 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
80
78
  // Track unread error and warning counts for logs badge
81
79
  const [unreadErrorCount, setUnreadErrorCount] = useState(0);
82
80
  const [unreadWarningCount, setUnreadWarningCount] = useState(0);
83
- // Tunnel state
84
- const [tunnelUrl, setTunnelUrl] = useState(null);
85
- const [tunnelCreating, setTunnelCreating] = useState(false);
86
- const [tunnelSpinnerFrame, setTunnelSpinnerFrame] = useState(0);
87
- const [localIp, setLocalIp] = useState("127.0.0.1");
88
- const [tunnelCopied, setTunnelCopied] = useState(false);
89
- // Subscribe to StateManager for unread error/warning count updates
81
+ // Track toast state
82
+ const [currentToast, setCurrentToast] = useState(null);
83
+ const [toastQueueLength, setToastQueueLength] = useState(0);
84
+ const [toastQueuePosition, setToastQueuePosition] = useState(null);
85
+ // Subscribe to StateManager for unread error/warning count and toast updates
90
86
  useEffect(() => {
91
87
  const stateManager = StateManager.getInstance();
92
- const updateCounts = () => {
93
- setUnreadErrorCount(stateManager.getUnreadErrorCount());
94
- setUnreadWarningCount(stateManager.getUnreadWarningCount());
88
+ const updateState = () => {
89
+ const state = stateManager.getState();
90
+ setUnreadErrorCount(state.unreadErrorCount);
91
+ setUnreadWarningCount(state.unreadWarningCount);
92
+ setCurrentToast(state.currentToast);
93
+ setToastQueueLength(state.toastQueueLength);
94
+ setToastQueuePosition(state.toastQueuePosition);
95
95
  };
96
- // Initial count
97
- updateCounts();
96
+ // Initial state
97
+ updateState();
98
98
  // Subscribe to changes
99
- const unsubscribe = stateManager.subscribe(updateCounts);
99
+ const unsubscribe = stateManager.subscribe(updateState);
100
100
  return () => {
101
101
  unsubscribe();
102
102
  };
103
103
  }, []);
104
104
  // Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
105
105
  const { panes, setPanes, isLoading, loadPanes, savePanes } = usePanes(panesFile, false);
106
- // Track intentionally closed panes to prevent race condition
107
- // When a user closes a pane, we add it to this set. If the worker detects
108
- // the pane is gone (which it will), we check this set first before re-saving.
109
- const intentionallyClosedPanes = React.useRef(new Set());
106
+ // Pane lifecycle manager - handles locking to prevent race conditions
107
+ // Replaces the old timeout-based intentionallyClosedPanes Set
108
+ const lifecycleManager = React.useMemo(() => PaneLifecycleManager.getInstance(), []);
109
+ // Clean up stale lifecycle operations periodically
110
+ useEffect(() => {
111
+ const cleanupInterval = setInterval(() => {
112
+ lifecycleManager.cleanupStaleOperations();
113
+ }, 60000); // Every 60 seconds
114
+ return () => clearInterval(cleanupInterval);
115
+ }, [lifecycleManager]);
110
116
  // Pane runner
111
- const { copyNonGitFiles, runCommandInternal, monitorTestOutput, monitorDevOutput, attachBackgroundWindow, } = usePaneRunner({
117
+ const { copyNonGitFiles, runCommandInternal, } = usePaneRunner({
112
118
  panes,
113
119
  savePanes,
114
120
  projectSettings,
@@ -119,16 +125,34 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
119
125
  const forceRepaint = () => {
120
126
  setForceRepaintTrigger((prev) => prev + 1);
121
127
  setShowRepaintSpinner(true);
128
+ // CRITICAL: Use Ink's official rerender method to force complete redraw
129
+ // When tmux clears the pane via selectLayout, Ink's output is lost
130
+ // Calling rerender forces Ink to redraw the entire component tree
131
+ if (rerenderRef?.current) {
132
+ rerenderRef.current(React.createElement(DmuxApp, {
133
+ panesFile,
134
+ projectName,
135
+ sessionName,
136
+ settingsFile,
137
+ projectRoot,
138
+ autoUpdater,
139
+ serverPort,
140
+ server,
141
+ controlPaneId,
142
+ rerenderRef,
143
+ }));
144
+ }
122
145
  // Hide spinner after a few frames (enough to trigger multiple renders)
123
- setTimeout(() => setShowRepaintSpinner(false), 100);
146
+ setTimeout(() => setShowRepaintSpinner(false), REPAINT_SPINNER_DURATION);
124
147
  };
125
148
  // Force repaint effect - ensures Ink re-renders when trigger changes
126
149
  useEffect(() => {
127
150
  if (forceRepaintTrigger > 0) {
128
151
  // Small delay to ensure terminal is ready
129
- const timer = setTimeout(() => {
152
+ const timer = setTimeout(async () => {
130
153
  try {
131
- execSync("tmux refresh-client", { stdio: "pipe" });
154
+ const tmuxService = TmuxService.getInstance();
155
+ await tmuxService.refreshClient();
132
156
  }
133
157
  catch { }
134
158
  }, 50);
@@ -137,48 +161,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
137
161
  }, [forceRepaintTrigger]);
138
162
  // Get local network IP on mount
139
163
  useEffect(() => {
140
- try {
141
- // Get local IP address (not 127.0.0.1)
142
- const result = execSync(`hostname -I 2>/dev/null || ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -1`, {
143
- encoding: "utf-8",
144
- stdio: "pipe",
145
- }).trim();
146
- if (result) {
147
- setLocalIp(result.split(" ")[0]); // Take first IP if multiple
148
- }
149
- }
150
- catch {
151
- // Fallback to 127.0.0.1
152
- setLocalIp("127.0.0.1");
153
- }
154
- }, []);
155
- // Spinner animation for tunnel creation
156
- useEffect(() => {
157
- if (!tunnelCreating)
158
- return;
159
- const spinnerInterval = setInterval(() => {
160
- setTunnelSpinnerFrame((prev) => (prev + 1) % 10);
161
- }, 80); // Update every 80ms
162
- return () => clearInterval(spinnerInterval);
163
- }, [tunnelCreating]);
164
- // Get current git branch on mount (only for dev builds)
165
- useEffect(() => {
166
- const isDev = process.env.DMUX_DEV === "true" || __dirname.includes("dist") === false;
167
- if (isDev) {
164
+ const getLocalIp = async () => {
168
165
  try {
169
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
166
+ // Get local IP address (not 127.0.0.1)
167
+ const { execSync } = await import("child_process");
168
+ const result = execSync(`hostname -I 2>/dev/null || ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -1`, {
170
169
  encoding: "utf-8",
171
170
  stdio: "pipe",
172
- cwd: projectRoot,
173
171
  }).trim();
174
- setCurrentBranch(branch);
172
+ if (result) {
173
+ setLocalIp(result.split(" ")[0]); // Take first IP if multiple
174
+ }
175
175
  }
176
176
  catch {
177
- // Not in a git repo or git not available
178
- setCurrentBranch(null);
177
+ // Fallback to 127.0.0.1
178
+ setLocalIp("127.0.0.1");
179
179
  }
180
- }
181
- }, [projectRoot]);
180
+ };
181
+ getLocalIp();
182
+ }, []);
183
+ // Spinner animation and branch detection now handled in hooks
182
184
  // Pane creation
183
185
  const { createNewPane: createNewPaneHook } = usePaneCreation({
184
186
  panes,
@@ -191,6 +193,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
191
193
  availableAgents,
192
194
  forceRepaint,
193
195
  });
196
+ // Initialize services
197
+ const { popupManager } = useServices({
198
+ // PopupManager config
199
+ sidebarWidth: SIDEBAR_WIDTH,
200
+ projectRoot: projectRoot || process.cwd(),
201
+ popupsSupported,
202
+ terminalWidth,
203
+ terminalHeight,
204
+ availableAgents,
205
+ agentChoice,
206
+ serverPort,
207
+ server,
208
+ settingsManager,
209
+ projectSettings,
210
+ // Callbacks
211
+ setStatusMessage,
212
+ setIgnoreInput,
213
+ savePanes,
214
+ loadPanes,
215
+ });
194
216
  // Listen for status updates with analysis data and merge into panes
195
217
  useEffect(() => {
196
218
  const statusDetector = getStatusDetector();
@@ -308,897 +330,144 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
308
330
  // findCardInDirection provided by useNavigation
309
331
  // savePanes moved to usePanes
310
332
  // applySmartLayout moved to utils/tmux
311
- const launchNewPanePopup = async () => {
312
- // Only launch popup if tmux supports it
313
- if (!popupsSupported) {
314
- setStatusMessage("Popups require tmux 3.2+");
315
- setTimeout(() => setStatusMessage(""), 3000);
316
- return;
333
+ // Helper function to handle agent choice and pane creation
334
+ const handlePaneCreationWithAgent = async (prompt) => {
335
+ const agents = availableAgents;
336
+ if (agents.length === 0) {
337
+ await createNewPaneHook(prompt);
317
338
  }
318
- try {
319
- // Resolve the popup script path from the project root
320
- // This handles both dev (tsx running from src) and prod (compiled to dist)
321
- const projectRootForPopup = __dirname.includes("/dist")
322
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
323
- : path.resolve(__dirname, ".."); // If in src/, go up one level
324
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "newPanePopup.js");
325
- // Calculate popup height as 80% of terminal height to allow room for file list
326
- const popupHeight = Math.floor(terminalHeight * 0.8);
327
- // Launch the popup non-blocking and track it
328
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [], {
329
- ...POPUP_POSITIONING.centeredWithSidebar(SIDEBAR_WIDTH),
330
- width: 90,
331
- height: popupHeight,
332
- title: " ✨ dmux - Create New Pane ",
333
- });
334
- LogService.getInstance().debug(`Popup created - PID: ${popupHandle.pid}, bounds: ${JSON.stringify(popupHandle.bounds)}`, "PopupTracking");
335
- // Wait for the popup to close
336
- const result = await popupHandle.resultPromise;
337
- // Clear active popup tracking
338
- LogService.getInstance().debug("Popup closed, clearing tracking", "PopupTracking");
339
- // Ignore input briefly after popup closes to prevent buffered keys
340
- setIgnoreInput(true);
341
- setTimeout(() => setIgnoreInput(false), 100);
342
- if (result.success && result.data) {
343
- // User entered a prompt - now decide which agent to use
344
- const promptValue = result.data;
345
- const agents = availableAgents;
346
- if (agents.length === 0) {
347
- await createNewPaneHook(promptValue);
348
- }
349
- else if (agents.length === 1) {
350
- await createNewPaneHook(promptValue, agents[0]);
351
- }
352
- else {
353
- // Multiple agents available - check for default agent setting first
354
- const settings = settingsManager.getSettings();
355
- if (settings.defaultAgent && agents.includes(settings.defaultAgent)) {
356
- // Use the default agent from settings
357
- await createNewPaneHook(promptValue, settings.defaultAgent);
358
- }
359
- else {
360
- // No default agent configured or default not available - show agent choice popup
361
- const selectedAgent = await launchAgentChoicePopup(promptValue);
362
- if (selectedAgent) {
363
- await createNewPaneHook(promptValue, selectedAgent);
364
- }
365
- }
366
- }
367
- }
368
- else if (result.cancelled) {
369
- // User pressed ESC - do nothing
370
- return;
371
- }
372
- else if (result.error) {
373
- setStatusMessage(`Popup error: ${result.error}`);
374
- setTimeout(() => setStatusMessage(""), 3000);
375
- }
376
- }
377
- catch (error) {
378
- setStatusMessage(`Failed to launch popup: ${error.message}`);
379
- setTimeout(() => setStatusMessage(""), 3000);
339
+ else if (agents.length === 1) {
340
+ await createNewPaneHook(prompt, agents[0]);
380
341
  }
381
- };
382
- const launchKebabMenuPopup = async (paneIndex) => {
383
- // Only launch popup if tmux supports it
384
- if (!popupsSupported) {
385
- setStatusMessage("Popups require tmux 3.2+");
386
- setTimeout(() => setStatusMessage(""), 3000);
387
- return;
388
- }
389
- const selectedPane = panes[paneIndex];
390
- if (!selectedPane) {
391
- return;
392
- }
393
- try {
394
- // Resolve the popup script path
395
- const projectRootForPopup = __dirname.includes("/dist")
396
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
397
- : path.resolve(__dirname, ".."); // If in src/, go up one level
398
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "kebabMenuPopup.js");
399
- // Get available actions for this pane
400
- const actions = getAvailableActions(selectedPane, projectSettings);
401
- const actionsJson = JSON.stringify(actions);
402
- // Launch the popup non-blocking and track it
403
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [selectedPane.slug, actionsJson], {
404
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
405
- width: 60,
406
- height: Math.min(20, actions.length + 5),
407
- title: `Menu: ${selectedPane.slug}`,
408
- });
409
- // Wait for the popup to close
410
- const result = await popupHandle.resultPromise;
411
- // Clear active popup tracking
412
- // Log the entire result for debugging
413
- LogService.getInstance().debug(`Kebab menu result: ${JSON.stringify(result)}`, "KebabMenu");
414
- if (result.success && result.data) {
415
- // User selected an action
416
- const actionId = result.data;
417
- LogService.getInstance().debug(`Action selected: ${actionId}`, "KebabMenu");
418
- // Handle merge action with dedicated popup
419
- if (actionId === PaneAction.MERGE) {
420
- LogService.getInstance().debug(`Merge action selected for pane: ${selectedPane.slug}`, "MergeAction");
421
- try {
422
- await launchMergePopup(selectedPane);
423
- LogService.getInstance().debug("Merge popup completed", "MergeAction");
424
- }
425
- catch (error) {
426
- LogService.getInstance().error("Merge popup error", "MergeAction", selectedPane.id, error instanceof Error ? error : undefined);
427
- setStatusMessage(`Merge popup failed: ${error}`);
428
- setTimeout(() => setStatusMessage(""), 3000);
429
- }
430
- }
431
- else {
432
- // Execute other actions through action system
433
- await actionSystem.executeAction(actionId, selectedPane, {
434
- mainBranch: getMainBranch(),
435
- });
436
- }
437
- }
438
- else if (result.cancelled) {
439
- // User pressed ESC - do nothing
440
- LogService.getInstance().debug("Kebab menu cancelled", "KebabMenu");
441
- return;
442
- }
443
- else if (result.error) {
444
- LogService.getInstance().error(`Kebab menu error: ${result.error}`, "KebabMenu");
445
- setStatusMessage(`Popup error: ${result.error}`);
446
- setTimeout(() => setStatusMessage(""), 3000);
342
+ else {
343
+ // Multiple agents available - check for default agent setting first
344
+ const settings = settingsManager.getSettings();
345
+ if (settings.defaultAgent && agents.includes(settings.defaultAgent)) {
346
+ await createNewPaneHook(prompt, settings.defaultAgent);
447
347
  }
448
348
  else {
449
- LogService.getInstance().warn(`Unexpected kebab menu result: ${JSON.stringify(result)}`, "KebabMenu");
450
- }
451
- }
452
- catch (error) {
453
- setStatusMessage(`Failed to launch popup: ${error.message}`);
454
- setTimeout(() => setStatusMessage(""), 3000);
455
- }
456
- };
457
- const launchConfirmPopup = async (title, message, yesLabel, noLabel) => {
458
- // Only launch popup if tmux supports it
459
- if (!popupsSupported) {
460
- setStatusMessage("Popups require tmux 3.2+");
461
- setTimeout(() => setStatusMessage(""), 3000);
462
- return false;
463
- }
464
- try {
465
- // Resolve the popup script path
466
- const projectRootForPopup = __dirname.includes("/dist")
467
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
468
- : path.resolve(__dirname, ".."); // If in src/, go up one level
469
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "confirmPopup.js");
470
- // Write data to temp file to avoid shell escaping issues
471
- const dataFile = `/tmp/dmux-confirm-${Date.now()}.json`;
472
- const dataJson = JSON.stringify({ title, message, yesLabel, noLabel });
473
- await fs.writeFile(dataFile, dataJson);
474
- // Launch the popup non-blocking and track it
475
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
476
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
477
- width: 60,
478
- height: 12,
479
- title: title || "Confirm",
480
- });
481
- // Wait for the popup to close
482
- const result = await popupHandle.resultPromise;
483
- // Clear active popup tracking
484
- // Clean up temp file
485
- try {
486
- await fs.unlink(dataFile);
487
- }
488
- catch { }
489
- if (result.success && result.data !== undefined) {
490
- return result.data;
491
- }
492
- else if (result.cancelled) {
493
- return false;
494
- }
495
- else if (result.error) {
496
- setStatusMessage(`Popup error: ${result.error}`);
497
- setTimeout(() => setStatusMessage(""), 3000);
498
- return false;
499
- }
500
- }
501
- catch (error) {
502
- setStatusMessage(`Failed to launch popup: ${error.message}`);
503
- setTimeout(() => setStatusMessage(""), 3000);
504
- }
505
- return false;
506
- };
507
- const launchAgentChoicePopup = async (prompt) => {
508
- // Only launch popup if tmux supports it
509
- if (!popupsSupported) {
510
- setStatusMessage("Popups require tmux 3.2+");
511
- setTimeout(() => setStatusMessage(""), 3000);
512
- return null;
513
- }
514
- try {
515
- // Resolve the popup script path
516
- const projectRootForPopup = __dirname.includes("/dist")
517
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
518
- : path.resolve(__dirname, ".."); // If in src/, go up one level
519
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "agentChoicePopup.js");
520
- const agentsJson = JSON.stringify(availableAgents);
521
- const defaultAgentArg = agentChoice || availableAgents[0] || "claude";
522
- // Launch the popup non-blocking and track it
523
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [agentsJson, defaultAgentArg], {
524
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
525
- width: 50,
526
- height: 10,
527
- title: "Select Agent",
528
- });
529
- // Wait for the popup to close
530
- const result = await popupHandle.resultPromise;
531
- // Clear active popup tracking
532
- if (result.success && result.data) {
533
- return result.data;
534
- }
535
- else if (result.cancelled) {
536
- return null;
537
- }
538
- else if (result.error) {
539
- setStatusMessage(`Popup error: ${result.error}`);
540
- setTimeout(() => setStatusMessage(""), 3000);
541
- return null;
542
- }
543
- }
544
- catch (error) {
545
- setStatusMessage(`Failed to launch popup: ${error.message}`);
546
- setTimeout(() => setStatusMessage(""), 3000);
547
- }
548
- return null;
549
- };
550
- const launchHooksPopup = async () => {
551
- // Only launch popup if tmux supports it
552
- if (!popupsSupported) {
553
- setStatusMessage("Popups require tmux 3.2+");
554
- setTimeout(() => setStatusMessage(""), 3000);
555
- return;
556
- }
557
- try {
558
- // Resolve the popup script path
559
- const projectRootForPopup = __dirname.includes("/dist")
560
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
561
- : path.resolve(__dirname, ".."); // If in src/, go up one level
562
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "hooksPopup.js");
563
- // Get hooks data
564
- const { hasHook } = await import("./utils/hooks.js");
565
- const allHookTypes = [
566
- "before_pane_create",
567
- "pane_created",
568
- "worktree_created",
569
- "before_pane_close",
570
- "pane_closed",
571
- "before_worktree_remove",
572
- "worktree_removed",
573
- "pre_merge",
574
- "post_merge",
575
- "run_test",
576
- "run_dev",
577
- ];
578
- const hooks = allHookTypes.map((hookName) => ({
579
- name: hookName,
580
- active: hasHook(projectRoot || process.cwd(), hookName),
581
- }));
582
- const hooksJson = JSON.stringify(hooks);
583
- // Launch the popup non-blocking and track it
584
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [hooksJson], {
585
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
586
- width: 70,
587
- height: 24,
588
- title: "🪝 Manage Hooks",
589
- });
590
- // Wait for the popup to close
591
- const result = await popupHandle.resultPromise;
592
- // Clear active popup tracking
593
- if (result.success && result.data?.action === "edit") {
594
- // Edit hooks using an agent
595
- const prompt = "I would like to edit my dmux hooks in .dmux-hooks, please read the instructions in there and ask me what I want to edit";
596
- // Choose agent
597
- const agents = availableAgents;
598
- if (agents.length === 0) {
599
- await createNewPaneHook(prompt);
349
+ // Show agent choice popup
350
+ const selectedAgent = await popupManager.launchAgentChoicePopup();
351
+ if (selectedAgent) {
352
+ await createNewPaneHook(prompt, selectedAgent);
600
353
  }
601
- else if (agents.length === 1) {
602
- await createNewPaneHook(prompt, agents[0]);
603
- }
604
- else {
605
- // Multiple agents available - check for default agent setting first
606
- const settings = settingsManager.getSettings();
607
- if (settings.defaultAgent && agents.includes(settings.defaultAgent)) {
608
- // Use the default agent from settings
609
- await createNewPaneHook(prompt, settings.defaultAgent);
610
- }
611
- else {
612
- // No default agent configured or default not available - show agent choice popup
613
- const selectedAgent = await launchAgentChoicePopup(prompt);
614
- if (selectedAgent) {
615
- await createNewPaneHook(prompt, selectedAgent);
616
- }
617
- }
618
- }
619
- }
620
- else if (result.success && result.data?.action === "view") {
621
- // View hooks file in editor - could implement this later
622
- setStatusMessage("View in editor not yet implemented");
623
- setTimeout(() => setStatusMessage(""), 2000);
624
- }
625
- else if (result.cancelled) {
626
- // User pressed ESC - do nothing
627
- return;
628
- }
629
- else if (result.error) {
630
- setStatusMessage(`Popup error: ${result.error}`);
631
- setTimeout(() => setStatusMessage(""), 3000);
632
- }
633
- }
634
- catch (error) {
635
- setStatusMessage(`Failed to launch popup: ${error.message}`);
636
- setTimeout(() => setStatusMessage(""), 3000);
637
- }
638
- };
639
- const launchLogsPopup = async () => {
640
- // Only launch popup if tmux supports it
641
- if (!popupsSupported) {
642
- setStatusMessage("Popups require tmux 3.2+");
643
- setTimeout(() => setStatusMessage(""), 3000);
644
- return;
645
- }
646
- try {
647
- // Resolve the popup script path
648
- const projectRootForPopup = __dirname.includes("/dist")
649
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
650
- : path.resolve(__dirname, ".."); // If in src/, go up one level
651
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "logsPopup.js");
652
- // Get logs from StateManager and write to temp file
653
- const stateManager = StateManager.getInstance();
654
- const allLogs = stateManager.getLogs();
655
- const stats = stateManager.getLogStats();
656
- const logsData = { logs: allLogs, stats };
657
- // Write data to temp file to avoid shell escaping issues with complex JSON
658
- const dataFile = `/tmp/dmux-logs-${Date.now()}.json`;
659
- const dataJson = JSON.stringify(logsData);
660
- await fs.writeFile(dataFile, dataJson);
661
- // Launch the popup with large positioning
662
- // Get tmux client dimensions (not process.stdout which is just the sidebar)
663
- const tmuxDims = execSync('tmux display-message -p "#{client_width},#{client_height}"', { encoding: "utf-8" }).trim();
664
- const [termWidth, termHeight] = tmuxDims.split(",").map(Number);
665
- // Launch the popup non-blocking and track it
666
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
667
- ...POPUP_POSITIONING.large(SIDEBAR_WIDTH, termWidth, termHeight),
668
- title: "🪵 dmux Logs",
669
- });
670
- // Wait for the popup to close
671
- const result = await popupHandle.resultPromise;
672
- // Clear active popup tracking
673
- // Clean up temp file
674
- try {
675
- await fs.unlink(dataFile);
676
- }
677
- catch (err) {
678
- // Ignore cleanup errors
679
- }
680
- // Popup closed - mark all logs as read
681
- if (result.success) {
682
- stateManager.markAllLogsAsRead();
683
- }
684
- }
685
- catch (error) {
686
- setStatusMessage(`Failed to launch popup: ${error.message}`);
687
- setTimeout(() => setStatusMessage(""), 3000);
688
- }
689
- };
690
- const launchShortcutsPopup = async () => {
691
- // Only launch popup if tmux supports it
692
- if (!popupsSupported) {
693
- setStatusMessage("Popups require tmux 3.2+");
694
- setTimeout(() => setStatusMessage(""), 3000);
695
- return;
696
- }
697
- try {
698
- // Resolve the popup script path
699
- const projectRootForPopup = __dirname.includes("/dist")
700
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
701
- : path.resolve(__dirname, ".."); // If in src/, go up one level
702
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "shortcutsPopup.js");
703
- // Prepare data for shortcuts popup
704
- const shortcutsData = {
705
- hasSidebarLayout: !!controlPaneId,
706
- showRemoteKey: !!server,
707
- };
708
- // Write data to temp file
709
- const dataFile = `/tmp/dmux-shortcuts-${Date.now()}.json`;
710
- const dataJson = JSON.stringify(shortcutsData);
711
- await fs.writeFile(dataFile, dataJson);
712
- // Launch the popup non-blocking and track it
713
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
714
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
715
- width: 50,
716
- height: 20,
717
- title: "⌨️ Keyboard Shortcuts",
718
- });
719
- // Wait for the popup to close
720
- const result = await popupHandle.resultPromise;
721
- // Clear active popup tracking
722
- // Clean up temp file
723
- try {
724
- await fs.unlink(dataFile);
725
354
  }
726
- catch (err) {
727
- // Ignore cleanup errors
728
- }
729
- }
730
- catch (error) {
731
- setStatusMessage(`Failed to launch popup: ${error.message}`);
732
- setTimeout(() => setStatusMessage(""), 3000);
733
355
  }
734
356
  };
735
- const launchRemotePopup = async () => {
736
- // Only launch popup if tmux supports it
737
- if (!popupsSupported) {
738
- setStatusMessage("Popups require tmux 3.2+");
739
- setTimeout(() => setStatusMessage(""), 3000);
357
+ // Helper function to handle action results recursively
358
+ const handleActionResult = async (result) => {
359
+ // Handle ActionResults from background callbacks (e.g., conflict resolution completion)
360
+ // This allows showing dialogs even when not in the normal action flow
361
+ if (!popupsSupported)
740
362
  return;
741
- }
742
- // Check if server is available and tunnel URL exists
743
- if (!server || !serverPort || !tunnelUrl) {
744
- setStatusMessage("Tunnel not ready");
745
- setTimeout(() => setStatusMessage(""), 3000);
746
- return;
747
- }
748
- try {
749
- // Resolve the popup script path
750
- const projectRootForPopup = __dirname.includes("/dist")
751
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
752
- : path.resolve(__dirname, ".."); // If in src/, go up one level
753
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "remotePopup.js");
754
- // Prepare status file with existing tunnel URL
755
- const tunnelStatusFile = `/tmp/dmux-tunnel-status-${Date.now()}.json`;
756
- await fs.writeFile(tunnelStatusFile, JSON.stringify({ url: tunnelUrl }));
757
- // Prepare data for remote popup
758
- const remoteData = {
759
- loading: false,
760
- serverPort: serverPort,
761
- statusFile: tunnelStatusFile,
762
- };
763
- // Write data to temp file
764
- const dataFile = `/tmp/dmux-remote-${Date.now()}.json`;
765
- await fs.writeFile(dataFile, JSON.stringify(remoteData));
766
- // Launch the popup non-blocking and track it
767
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
768
- ...POPUP_POSITIONING.centeredWithSidebar(SIDEBAR_WIDTH),
769
- width: 60,
770
- height: 30,
771
- title: "🌐 Remote Access",
772
- });
773
- // Wait for the popup to close
774
- const result = await popupHandle.resultPromise;
775
- // Clear active popup tracking
776
- // Show "Copied!" message if URL was copied
777
- // Note: result is the parsed JSON directly, not wrapped in PopupResult.data
778
- if (result && result.copied) {
779
- setTunnelCopied(true);
780
- setTimeout(() => setTunnelCopied(false), 2000);
781
- }
782
- // Clean up temp files
783
- try {
784
- await fs.unlink(dataFile);
785
- }
786
- catch (err) {
787
- // Ignore cleanup errors
788
- }
789
- try {
790
- await fs.unlink(tunnelStatusFile);
791
- }
792
- catch (err) {
793
- // Ignore cleanup errors (file might not exist yet)
794
- }
795
- }
796
- catch (error) {
797
- setStatusMessage(`Failed to launch popup: ${error.message}`);
798
- setTimeout(() => setStatusMessage(""), 3000);
799
- }
800
- };
801
- const launchSettingsPopup = async () => {
802
- // Only launch popup if tmux supports it
803
- if (!popupsSupported) {
804
- setStatusMessage("Popups require tmux 3.2+");
805
- setTimeout(() => setStatusMessage(""), 3000);
806
- return;
807
- }
808
- try {
809
- // Resolve the popup script path
810
- const projectRootForPopup = __dirname.includes("/dist")
811
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
812
- : path.resolve(__dirname, ".."); // If in src/, go up one level
813
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "settingsPopup.js");
814
- // Prepare settings data for popup
815
- const settingsData = {
816
- settingDefinitions: SETTING_DEFINITIONS,
817
- settings: settingsManager.getSettings(),
818
- globalSettings: settingsManager.getGlobalSettings(),
819
- projectSettings: settingsManager.getProjectSettings(),
820
- };
821
- const settingsJson = JSON.stringify(settingsData);
822
- // Launch the popup non-blocking and track it
823
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [settingsJson], {
824
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
825
- width: 70,
826
- height: Math.min(25, SETTING_DEFINITIONS.length + 8),
827
- title: "⚙️ Settings",
828
- });
829
- // Wait for the popup to close
830
- const result = await popupHandle.resultPromise;
831
- // Clear active popup tracking
832
- if (result.success) {
833
- // Check if this is an action result (action field at top level)
834
- if (result.action) {
835
- // Action type setting (like 'hooks')
836
- if (result.action === "hooks") {
837
- // Launch hooks popup
838
- await launchHooksPopup();
839
- }
840
- }
841
- else if (result.data) {
842
- // Regular setting change (result.data contains the setting)
843
- const { key, value, scope } = result.data;
844
- settingsManager.updateSetting(key, value, scope);
845
- setStatusMessage(`Setting saved (${scope})`);
846
- setTimeout(() => setStatusMessage(""), 2000);
363
+ // Handle the result type and show appropriate dialog
364
+ if (result.type === "confirm") {
365
+ const confirmed = await popupManager.launchConfirmPopup(result.title || "Confirm", result.message, result.confirmLabel, result.cancelLabel);
366
+ if (confirmed && result.onConfirm) {
367
+ const nextResult = await result.onConfirm();
368
+ // Recursively handle nested results
369
+ if (nextResult) {
370
+ await handleActionResult(nextResult);
847
371
  }
848
372
  }
849
- else if (result.cancelled) {
850
- // User pressed ESC - do nothing
851
- return;
852
- }
853
- else if (result.error) {
854
- setStatusMessage(`Popup error: ${result.error}`);
855
- setTimeout(() => setStatusMessage(""), 3000);
373
+ else if (!confirmed && result.onCancel) {
374
+ const nextResult = await result.onCancel();
375
+ if (nextResult) {
376
+ await handleActionResult(nextResult);
377
+ }
856
378
  }
857
379
  }
858
- catch (error) {
859
- setStatusMessage(`Failed to launch popup: ${error.message}`);
860
- setTimeout(() => setStatusMessage(""), 3000);
861
- }
862
- };
863
- const launchMergePopup = async (pane) => {
864
- LogService.getInstance().debug(`launchMergePopup called for pane: ${pane.slug}`, "MergePopup");
865
- // Only launch popup if tmux supports it
866
- if (!popupsSupported) {
867
- LogService.getInstance().warn("Popups not supported", "MergePopup");
868
- setStatusMessage("Popups require tmux 3.2+");
869
- setTimeout(() => setStatusMessage(""), 3000);
870
- return;
871
- }
872
- if (!pane.worktreePath) {
873
- LogService.getInstance().warn("No worktree path for pane", "MergePopup", pane.id);
874
- setStatusMessage("This pane has no worktree to merge");
875
- setTimeout(() => setStatusMessage(""), 3000);
876
- return;
877
- }
878
- try {
879
- // Resolve the popup script path
880
- const projectRootForPopup = __dirname.includes("/dist")
881
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
882
- : path.resolve(__dirname, ".."); // If in src/, go up one level
883
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "mergePopup.js");
884
- LogService.getInstance().debug(`Popup script path: ${popupScriptPath}`, "MergePopup");
885
- // Check if popup script exists
886
- try {
887
- await fs.access(popupScriptPath);
888
- LogService.getInstance().debug("Popup script exists", "MergePopup");
889
- }
890
- catch {
891
- LogService.getInstance().error(`Popup script NOT found at: ${popupScriptPath}`, "MergePopup");
892
- setStatusMessage(`Merge popup script not found: ${popupScriptPath}`);
893
- setTimeout(() => setStatusMessage(""), 5000);
380
+ else if (result.type === "choice") {
381
+ if (!result.options || !result.onSelect)
894
382
  return;
895
- }
896
- // Prepare merge data
897
- const mainRepoPath = pane.worktreePath.replace(/\/\.dmux\/worktrees\/[^/]+$/, "");
898
- const mergeData = {
899
- paneSlug: pane.slug,
900
- worktreePath: pane.worktreePath,
901
- mainRepoPath,
902
- mainBranch: getMainBranch(),
903
- };
904
- // Write data to temp file
905
- const dataFile = `/tmp/dmux-merge-${Date.now()}.json`;
906
- await fs.writeFile(dataFile, JSON.stringify(mergeData));
907
- LogService.getInstance().debug(`Merge data written to: ${dataFile}`, "MergePopup");
908
- LogService.getInstance().debug(`Merge data: ${JSON.stringify(mergeData)}`, "MergePopup");
909
- // Launch the popup non-blocking and track it
910
- LogService.getInstance().debug("Launching merge popup...", "MergePopup");
911
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
912
- ...POPUP_POSITIONING.large(SIDEBAR_WIDTH, terminalWidth, terminalHeight),
913
- width: 80,
914
- height: 30,
915
- title: `🔀 Merge: ${pane.slug}`,
916
- });
917
- LogService.getInstance().debug(`Popup launched, PID: ${popupHandle.pid}`, "MergePopup");
918
- // Wait for the popup to close
919
- const result = await popupHandle.resultPromise;
920
- // Clear active popup tracking
921
- // Clean up temp file
922
- try {
923
- await fs.unlink(dataFile);
924
- }
925
- catch { }
926
- if (result.success && result.data?.merged) {
927
- if (result.data.closedPane) {
928
- // Pane was closed during merge, refresh pane list
929
- await loadPanes();
930
- setStatusMessage("Merge complete, pane closed");
931
- }
932
- else {
933
- setStatusMessage("Merge complete");
383
+ const selectedId = await popupManager.launchChoicePopup(result.title || "Choose Option", result.message, result.options);
384
+ if (selectedId) {
385
+ const nextResult = await result.onSelect(selectedId);
386
+ // Recursively handle nested results
387
+ if (nextResult) {
388
+ await handleActionResult(nextResult);
934
389
  }
935
- setTimeout(() => setStatusMessage(""), 3000);
936
- }
937
- else if (result.cancelled) {
938
- // User cancelled
939
- return;
940
- }
941
- else if (result.error) {
942
- setStatusMessage(`Merge error: ${result.error}`);
943
- setTimeout(() => setStatusMessage(""), 3000);
944
390
  }
945
391
  }
946
- catch (error) {
947
- setStatusMessage(`Failed to launch merge popup: ${error.message}`);
948
- setTimeout(() => setStatusMessage(""), 3000);
949
- }
950
- };
951
- const launchChoicePopup = async (title, message, options) => {
952
- // Only launch popup if tmux supports it
953
- if (!popupsSupported) {
954
- setStatusMessage("Popups require tmux 3.2+");
955
- setTimeout(() => setStatusMessage(""), 3000);
956
- return null;
957
- }
958
- try {
959
- // Resolve the popup script path
960
- const projectRootForPopup = __dirname.includes("/dist")
961
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
962
- : path.resolve(__dirname, ".."); // If in src/, go up one level
963
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "choicePopup.js");
964
- // Write data to temp file to avoid shell escaping issues
965
- const dataFile = `/tmp/dmux-choice-${Date.now()}.json`;
966
- const dataJson = JSON.stringify({ title, message, options });
967
- await fs.writeFile(dataFile, dataJson);
968
- // Launch the popup non-blocking and track it
969
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
970
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
971
- width: 70,
972
- height: Math.min(25, options.length * 3 + 8),
973
- title: title || "Choose Option",
974
- });
975
- // Wait for the popup to close
976
- const result = await popupHandle.resultPromise;
977
- // Clear active popup tracking
978
- // Clean up temp file
979
- try {
980
- await fs.unlink(dataFile);
981
- }
982
- catch { }
983
- if (result.success && result.data) {
984
- return result.data;
985
- }
986
- else if (result.cancelled) {
987
- return null;
988
- }
989
- else if (result.error) {
990
- setStatusMessage(`Popup error: ${result.error}`);
991
- setTimeout(() => setStatusMessage(""), 3000);
992
- return null;
392
+ else if (result.type === "input") {
393
+ if (!result.onSubmit)
394
+ return;
395
+ const inputValue = await popupManager.launchInputPopup(result.title || "Input", result.message, result.placeholder, result.defaultValue);
396
+ if (inputValue !== null) {
397
+ const nextResult = await result.onSubmit(inputValue);
398
+ // Recursively handle nested results
399
+ if (nextResult) {
400
+ await handleActionResult(nextResult);
401
+ }
993
402
  }
994
403
  }
995
- catch (error) {
996
- setStatusMessage(`Failed to launch popup: ${error.message}`);
997
- setTimeout(() => setStatusMessage(""), 3000);
998
- }
999
- return null;
1000
- };
1001
- const launchInputPopup = async (title, message, placeholder, defaultValue) => {
1002
- // Only launch popup if tmux supports it
1003
- if (!popupsSupported) {
1004
- setStatusMessage("Popups require tmux 3.2+");
1005
- setTimeout(() => setStatusMessage(""), 3000);
1006
- return null;
1007
- }
1008
- try {
1009
- // Resolve the popup script path
1010
- const projectRootForPopup = __dirname.includes("/dist")
1011
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
1012
- : path.resolve(__dirname, ".."); // If in src/, go up one level
1013
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "inputPopup.js");
1014
- // Write data to temp file to avoid shell escaping issues
1015
- const dataFile = `/tmp/dmux-input-${Date.now()}.json`;
1016
- const dataJson = JSON.stringify({
1017
- title,
1018
- message,
1019
- placeholder,
1020
- defaultValue,
1021
- });
1022
- await fs.writeFile(dataFile, dataJson);
1023
- // Launch the popup non-blocking and track it
1024
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
1025
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
1026
- width: 70,
1027
- height: 15,
1028
- title: title || "Input",
1029
- });
1030
- // Wait for the popup to close
1031
- const result = await popupHandle.resultPromise;
1032
- // Clear active popup tracking
1033
- // Clean up temp file
1034
- try {
1035
- await fs.unlink(dataFile);
1036
- }
1037
- catch { }
1038
- if (result.success && result.data !== undefined) {
1039
- return result.data;
1040
- }
1041
- else if (result.cancelled) {
1042
- return null;
1043
- }
1044
- else if (result.error) {
1045
- setStatusMessage(`Popup error: ${result.error}`);
1046
- setTimeout(() => setStatusMessage(""), 3000);
1047
- return null;
404
+ else if (result.type === "navigation") {
405
+ // Navigate to target pane if specified
406
+ if (result.targetPaneId) {
407
+ const targetPane = panes.find(p => p.id === result.targetPaneId);
408
+ if (targetPane) {
409
+ try {
410
+ TmuxService.getInstance().selectPane(targetPane.paneId);
411
+ }
412
+ catch (error) {
413
+ console.error('[onActionResult] Failed to navigate to pane:', error);
414
+ }
415
+ }
1048
416
  }
1049
- }
1050
- catch (error) {
1051
- setStatusMessage(`Failed to launch popup: ${error.message}`);
1052
- setTimeout(() => setStatusMessage(""), 3000);
1053
- }
1054
- return null;
1055
- };
1056
- const launchProgressPopup = async (message, type = "info", timeout = 2000) => {
1057
- // Only launch popup if tmux supports it
1058
- if (!popupsSupported) {
1059
- setStatusMessage(message);
1060
- setTimeout(() => setStatusMessage(""), timeout);
1061
- return;
1062
- }
1063
- try {
1064
- // Resolve the popup script path
1065
- const projectRootForPopup = __dirname.includes("/dist")
1066
- ? path.resolve(__dirname, "..") // If in dist/, go up one level
1067
- : path.resolve(__dirname, ".."); // If in src/, go up one level
1068
- const popupScriptPath = path.join(projectRootForPopup, "dist", "popups", "progressPopup.js");
1069
- // Write data to temp file to avoid shell escaping issues
1070
- const dataFile = `/tmp/dmux-progress-${Date.now()}.json`;
1071
- const dataJson = JSON.stringify({ message, type, timeout });
1072
- await fs.writeFile(dataFile, dataJson);
1073
- // Launch the popup - position at top, 1 char right of sidebar
1074
- // Height depends on message length
1075
- const lines = Math.ceil(message.length / 60) + 3; // Estimate lines needed
1076
- const titleText = type === "success"
1077
- ? "✓ Success"
1078
- : type === "error"
1079
- ? "✗ Error"
1080
- : type === "info"
1081
- ? "ℹ Info"
1082
- : "Progress";
1083
- // Launch the popup non-blocking and track it
1084
- const popupHandle = launchNodePopupNonBlocking(popupScriptPath, [dataFile], {
1085
- ...POPUP_POSITIONING.standard(SIDEBAR_WIDTH),
1086
- width: 70,
1087
- height: Math.min(15, lines + 4),
1088
- title: titleText,
1089
- });
1090
- // Wait for the popup to close
1091
- await popupHandle.resultPromise;
1092
- // Clear active popup tracking
1093
- // Clean up temp file
1094
- try {
1095
- await fs.unlink(dataFile);
417
+ // Show message if dismissable
418
+ if (result.message && result.dismissable) {
419
+ await popupManager.launchProgressPopup(result.message, "info", 3000);
1096
420
  }
1097
- catch { }
1098
421
  }
1099
- catch (error) {
1100
- // Fallback to inline message
1101
- setStatusMessage(message);
1102
- setTimeout(() => setStatusMessage(""), timeout);
422
+ else if (result.type === "info" ||
423
+ result.type === "success" ||
424
+ result.type === "error") {
425
+ // Use toast notification instead of popup for better UX
426
+ const { default: stateManager } = await import("./shared/StateManager.js");
427
+ stateManager.showToast(result.message, result.type);
1103
428
  }
1104
429
  };
1105
- // Action system - initialized after popup launchers are defined
430
+ // Action system - initialized after services are defined
1106
431
  const actionSystem = useActionSystem({
1107
432
  panes,
1108
433
  savePanes,
1109
434
  sessionName,
1110
435
  projectName,
1111
- onPaneRemove: (paneId) => {
1112
- // Mark the pane as intentionally closed to prevent race condition with worker
1113
- intentionallyClosedPanes.current.add(paneId);
436
+ onPaneRemove: async (paneId) => {
437
+ // Mark pane as closing to prevent race condition with worker
438
+ await lifecycleManager.beginClose(paneId, 'user requested');
1114
439
  // Remove from panes list
1115
440
  const updatedPanes = panes.filter((p) => p.paneId !== paneId);
1116
441
  savePanes(updatedPanes);
1117
- // Clean up after a delay
1118
- setTimeout(() => {
1119
- intentionallyClosedPanes.current.delete(paneId);
1120
- }, 5000);
442
+ // Mark close as completed (no more lock needed)
443
+ await lifecycleManager.completeClose(paneId);
1121
444
  },
445
+ onActionResult: handleActionResult,
1122
446
  forceRepaint,
1123
447
  popupLaunchers: popupsSupported
1124
448
  ? {
1125
- launchConfirmPopup,
1126
- launchChoicePopup,
1127
- launchInputPopup,
1128
- launchProgressPopup,
449
+ launchConfirmPopup: popupManager.launchConfirmPopup.bind(popupManager),
450
+ launchChoicePopup: popupManager.launchChoicePopup.bind(popupManager),
451
+ launchInputPopup: popupManager.launchInputPopup.bind(popupManager),
452
+ launchProgressPopup: popupManager.launchProgressPopup.bind(popupManager),
1129
453
  }
1130
454
  : undefined,
1131
455
  });
1132
456
  // Auto-show new pane dialog removed - users can press 'n' to create panes via popup
1133
457
  // Periodic enforcement of control pane size and content pane rebalancing (left sidebar at 40 chars)
1134
- useEffect(() => {
1135
- if (!controlPaneId) {
1136
- return; // No sidebar layout configured
1137
- }
1138
- // Enforce sidebar width immediately on mount
1139
- enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH);
1140
- // Debounce resize handler to prevent infinite loops
1141
- let resizeTimeout = null;
1142
- let isApplyingLayout = false;
1143
- const handleResize = () => {
1144
- // Skip if we're already applying a layout (prevents loops)
1145
- if (isApplyingLayout) {
1146
- return;
1147
- }
1148
- // Clear any pending resize
1149
- if (resizeTimeout) {
1150
- clearTimeout(resizeTimeout);
1151
- }
1152
- // Debounce: wait 500ms after last resize event (prevents excessive recalculations)
1153
- resizeTimeout = setTimeout(() => {
1154
- // Only enforce if not showing dialogs (to avoid interference)
1155
- const hasActiveDialog = actionSystem.actionState.showConfirmDialog ||
1156
- actionSystem.actionState.showChoiceDialog ||
1157
- actionSystem.actionState.showInputDialog ||
1158
- actionSystem.actionState.showProgressDialog ||
1159
- !!showCommandPrompt ||
1160
- showFileCopyPrompt ||
1161
- isCreatingPane ||
1162
- runningCommand ||
1163
- isUpdating;
1164
- if (!hasActiveDialog) {
1165
- isApplyingLayout = true;
1166
- // Only enforce sidebar width when terminal resizes
1167
- enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH);
1168
- // Force Ink to repaint after resize to prevent blank dmux pane
1169
- forceRepaint();
1170
- // Reset flag after a brief delay
1171
- setTimeout(() => {
1172
- isApplyingLayout = false;
1173
- }, 100);
1174
- }
1175
- }, 500);
1176
- };
1177
- // Listen to stdout resize events
1178
- process.stdout.on("resize", handleResize);
1179
- // Also listen for SIGWINCH and SIGUSR1 (tmux hook sends USR1)
1180
- process.on("SIGWINCH", handleResize);
1181
- process.on("SIGUSR1", handleResize);
1182
- return () => {
1183
- process.stdout.off("resize", handleResize);
1184
- process.off("SIGWINCH", handleResize);
1185
- process.off("SIGUSR1", handleResize);
1186
- if (resizeTimeout) {
1187
- clearTimeout(resizeTimeout);
1188
- }
1189
- };
1190
- }, [
458
+ useLayoutManagement({
1191
459
  controlPaneId,
1192
- actionSystem.actionState.showConfirmDialog,
1193
- actionSystem.actionState.showChoiceDialog,
1194
- actionSystem.actionState.showInputDialog,
1195
- actionSystem.actionState.showProgressDialog,
1196
- showCommandPrompt,
1197
- showFileCopyPrompt,
1198
- isCreatingPane,
1199
- runningCommand,
1200
- isUpdating,
1201
- ]);
460
+ hasActiveDialog: actionSystem.actionState.showConfirmDialog ||
461
+ actionSystem.actionState.showChoiceDialog ||
462
+ actionSystem.actionState.showInputDialog ||
463
+ actionSystem.actionState.showProgressDialog ||
464
+ !!showCommandPrompt ||
465
+ showFileCopyPrompt ||
466
+ isCreatingPane ||
467
+ runningCommand ||
468
+ isUpdating,
469
+ onForceRepaint: forceRepaint,
470
+ });
1202
471
  // Monitor agent status across panes (returns a map of pane ID to status)
1203
472
  const agentStatuses = useAgentStatus({
1204
473
  panes,
@@ -1209,9 +478,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1209
478
  !!showCommandPrompt ||
1210
479
  showFileCopyPrompt,
1211
480
  onPaneRemoved: (paneId) => {
1212
- // Check if this pane was intentionally closed
481
+ // Check if this pane is being closed intentionally or is locked
1213
482
  // If so, don't re-save - the close action already handled it
1214
- if (intentionallyClosedPanes.current.has(paneId)) {
483
+ if (lifecycleManager.isClosing(paneId) || lifecycleManager.isLocked(paneId)) {
1215
484
  return;
1216
485
  }
1217
486
  // Pane was removed unexpectedly (e.g., user killed tmux pane manually)
@@ -1220,415 +489,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1220
489
  savePanes(updatedPanes);
1221
490
  },
1222
491
  });
1223
- const createNewPane = async (prompt, agent) => {
1224
- setIsCreatingPane(true);
1225
- setStatusMessage("Generating slug...");
1226
- const slug = await generateSlug(prompt);
1227
- setStatusMessage(`Creating worktree: ${slug}...`);
1228
- // Get git root directory for consistent worktree placement
1229
- let projectRoot;
1230
- try {
1231
- projectRoot = execSync("git rev-parse --show-toplevel", {
1232
- encoding: "utf-8",
1233
- stdio: "pipe",
1234
- }).trim();
1235
- }
1236
- catch {
1237
- // Fallback to current directory if not in a git repo
1238
- projectRoot = process.cwd();
1239
- }
1240
- // Create worktree path inside .dmux/worktrees directory
1241
- const worktreePath = path.join(projectRoot, ".dmux", "worktrees", slug);
1242
- // Get the original pane ID (where dmux is running) before clearing
1243
- const originalPaneId = execSync('tmux display-message -p "#{pane_id}"', {
1244
- encoding: "utf-8",
1245
- }).trim();
1246
- // Minimal clearing to avoid layout shifts
1247
- process.stdout.write("\x1b[2J\x1b[H");
1248
- // Get current pane count to determine layout
1249
- const paneCount = parseInt(execSync("tmux list-panes | wc -l", { encoding: "utf-8" }).trim());
1250
- // Enable pane borders to show titles
1251
- try {
1252
- execSync(`tmux set-option -g pane-border-status top`, { stdio: "pipe" });
1253
- }
1254
- catch {
1255
- // Ignore if already set or fails
1256
- }
1257
- // Create new pane
1258
- const paneInfo = execSync(`tmux split-window -h -P -F '#{pane_id}'`, {
1259
- encoding: "utf-8",
1260
- }).trim();
1261
- // Wait for pane creation to settle
1262
- await new Promise((resolve) => setTimeout(resolve, 500));
1263
- // Set pane title to match the slug
1264
- try {
1265
- execSync(`tmux select-pane -t '${paneInfo}' -T "${slug}"`, {
1266
- stdio: "pipe",
1267
- });
1268
- }
1269
- catch {
1270
- // Ignore if setting title fails
1271
- }
1272
- // Don't apply global layouts - let content panes arrange naturally
1273
- // Only enforce sidebar width
1274
- try {
1275
- const controlPaneId = execSync('tmux display-message -p "#{pane_id}"', {
1276
- encoding: "utf-8",
1277
- }).trim();
1278
- enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH);
1279
- }
1280
- catch { }
1281
- // Create git worktree and cd into it
1282
- // This MUST happen before launching Claude to ensure we're in the right directory
1283
- try {
1284
- // First, create the worktree and cd into it as a single command
1285
- // Use ; instead of && to ensure cd runs even if worktree already exists
1286
- const worktreeCmd = `git worktree add "${worktreePath}" -b ${slug} 2>/dev/null ; cd "${worktreePath}"`;
1287
- execSync(`tmux send-keys -t '${paneInfo}' '${worktreeCmd}' Enter`, {
1288
- stdio: "pipe",
1289
- });
1290
- // Wait longer for worktree creation and cd to complete
1291
- // This is critical - if we don't wait long enough, Claude will start in the wrong directory
1292
- await new Promise((resolve) => setTimeout(resolve, 2500));
1293
- // Verify we're in the worktree directory by sending pwd command
1294
- execSync(`tmux send-keys -t '${paneInfo}' 'echo "Worktree created at:" && pwd' Enter`, { stdio: "pipe" });
1295
- await new Promise((resolve) => setTimeout(resolve, 500));
1296
- setStatusMessage(agent
1297
- ? `Worktree created, launching ${agent === "opencode" ? "opencode" : "Claude"}...`
1298
- : "Worktree created.");
1299
- }
1300
- catch (error) {
1301
- // Log error but continue - worktree creation is essential
1302
- setStatusMessage(`Warning: Worktree issue: ${error}`);
1303
- // Even if worktree creation failed, try to cd to the directory in case it exists
1304
- execSync(`tmux send-keys -t '${paneInfo}' 'cd "${worktreePath}" 2>/dev/null || (echo "ERROR: Failed to create/enter worktree ${slug}" && pwd)' Enter`, { stdio: "pipe" });
1305
- await new Promise((resolve) => setTimeout(resolve, 1000));
1306
- }
1307
- // Prepare and send the agent command
1308
- let escapedCmd = "";
1309
- if (agent === "claude") {
1310
- // Claude should always be launched AFTER we're in the worktree directory
1311
- let claudeCmd;
1312
- if (prompt && prompt.trim()) {
1313
- const escapedPrompt = prompt
1314
- .replace(/\\/g, "\\\\")
1315
- .replace(/"/g, '\\"')
1316
- .replace(/`/g, "\\`")
1317
- .replace(/\$/g, "\\$");
1318
- claudeCmd = `claude "${escapedPrompt}" --permission-mode=acceptEdits`;
1319
- }
1320
- else {
1321
- claudeCmd = `claude --permission-mode=acceptEdits`;
1322
- }
1323
- // Send Claude command to new pane
1324
- escapedCmd = claudeCmd.replace(/'/g, "'\\''");
1325
- execSync(`tmux send-keys -t '${paneInfo}' '${escapedCmd}'`, {
1326
- stdio: "pipe",
1327
- });
1328
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: "pipe" });
1329
- }
1330
- else if (agent === "opencode") {
1331
- // opencode: start the TUI, then paste the prompt and submit
1332
- const openCoderCmd = `opencode`;
1333
- const escapedOpenCmd = openCoderCmd.replace(/'/g, "'\\''");
1334
- execSync(`tmux send-keys -t '${paneInfo}' '${escapedOpenCmd}'`, {
1335
- stdio: "pipe",
1336
- });
1337
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: "pipe" });
1338
- if (prompt && prompt.trim()) {
1339
- await new Promise((resolve) => setTimeout(resolve, 1500));
1340
- const bufName = `dmux_prompt_${Date.now()}`;
1341
- const promptEsc = prompt.replace(/\\/g, "\\\\").replace(/'/g, "'\\''");
1342
- execSync(`tmux set-buffer -b '${bufName}' -- '${promptEsc}'`, {
1343
- stdio: "pipe",
1344
- });
1345
- execSync(`tmux paste-buffer -b '${bufName}' -t '${paneInfo}'`, {
1346
- stdio: "pipe",
1347
- });
1348
- await new Promise((resolve) => setTimeout(resolve, 200));
1349
- execSync(`tmux delete-buffer -b '${bufName}'`, { stdio: "pipe" });
1350
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: "pipe" });
1351
- }
1352
- }
1353
- if (agent === "claude") {
1354
- // Monitor for Claude Code trust prompt and auto-respond
1355
- const autoApproveTrust = async () => {
1356
- // Wait for Claude to start up before checking for prompts
1357
- await new Promise((resolve) => setTimeout(resolve, 800));
1358
- const maxChecks = 100; // 100 checks * 100ms = 10 seconds total
1359
- const checkInterval = 100; // Check every 100ms
1360
- let lastContent = "";
1361
- let stableContentCount = 0;
1362
- let promptHandled = false;
1363
- // More comprehensive trust prompt patterns
1364
- const trustPromptPatterns = [
1365
- /Do you trust the files in this folder\?/i,
1366
- /Trust the files in this workspace\?/i,
1367
- /Do you trust the authors of the files/i,
1368
- /Do you want to trust this workspace\?/i,
1369
- /trust.*files.*folder/i,
1370
- /trust.*workspace/i,
1371
- /Do you trust/i,
1372
- /Trust this folder/i,
1373
- /trust.*directory/i,
1374
- /permission.*grant/i,
1375
- /allow.*access/i,
1376
- /workspace.*trust/i,
1377
- /accept.*edits/i, // Claude's accept edits prompt
1378
- /permission.*mode/i, // Permission mode prompt
1379
- /allow.*claude/i, // Allow Claude prompt
1380
- /\[y\/n\]/i, // Common yes/no prompt pattern
1381
- /\(y\/n\)/i,
1382
- /Yes\/No/i,
1383
- /\[Y\/n\]/i, // Default yes pattern
1384
- /press.*enter.*accept/i, // Press enter to accept
1385
- /press.*enter.*continue/i, // Press enter to continue
1386
- /❯\s*1\.\s*Yes,\s*proceed/i, // New Claude numbered menu format
1387
- /Enter to confirm.*Esc to exit/i, // New Claude confirmation format
1388
- /1\.\s*Yes,\s*proceed/i, // Yes proceed option
1389
- /2\.\s*No,\s*exit/i, // No exit option
1390
- ];
1391
- for (let i = 0; i < maxChecks; i++) {
1392
- await new Promise((resolve) => setTimeout(resolve, checkInterval));
1393
- try {
1394
- // Capture the pane content
1395
- const paneContent = capturePaneContent(paneInfo, 30);
1396
- if (i % 10 === 0) {
1397
- // Log every 10 checks (every second)
1398
- }
1399
- // Check if content has stabilized (same for 3 checks = prompt is waiting)
1400
- if (paneContent === lastContent) {
1401
- stableContentCount++;
1402
- }
1403
- else {
1404
- stableContentCount = 0;
1405
- lastContent = paneContent;
1406
- }
1407
- // Look for trust prompt in the current content
1408
- const hasTrustPrompt = trustPromptPatterns.some((pattern) => pattern.test(paneContent));
1409
- // Also check if we see specific Claude permission text
1410
- const hasClaudePermissionPrompt = paneContent.includes("Do you trust") ||
1411
- paneContent.includes("trust the files") ||
1412
- paneContent.includes("permission") ||
1413
- paneContent.includes("allow") ||
1414
- (paneContent.includes("folder") && paneContent.includes("?"));
1415
- if ((hasTrustPrompt || hasClaudePermissionPrompt) &&
1416
- !promptHandled) {
1417
- // Content is stable and we found a prompt
1418
- if (stableContentCount >= 2) {
1419
- // Check if this is the new Claude numbered menu format
1420
- const isNewClaudeFormat = /❯\s*1\.\s*Yes,\s*proceed/i.test(paneContent) ||
1421
- /Enter to confirm.*Esc to exit/i.test(paneContent);
1422
- if (isNewClaudeFormat) {
1423
- // For new Claude format, just press Enter to confirm default "Yes, proceed"
1424
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, {
1425
- stdio: "pipe",
1426
- });
1427
- }
1428
- else {
1429
- // Try multiple response methods for older formats
1430
- // Method 1: Send 'y' followed by Enter (most explicit)
1431
- execSync(`tmux send-keys -t '${paneInfo}' 'y'`, {
1432
- stdio: "pipe",
1433
- });
1434
- await new Promise((resolve) => setTimeout(resolve, 50));
1435
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, {
1436
- stdio: "pipe",
1437
- });
1438
- // Method 2: Just Enter (if it's a yes/no with default yes)
1439
- await new Promise((resolve) => setTimeout(resolve, 100));
1440
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, {
1441
- stdio: "pipe",
1442
- });
1443
- }
1444
- // Mark as handled to avoid duplicate responses
1445
- promptHandled = true;
1446
- // Wait and check if prompt is gone
1447
- await new Promise((resolve) => setTimeout(resolve, 500));
1448
- // Verify the prompt is gone
1449
- const updatedContent = capturePaneContent(paneInfo, 10);
1450
- // If trust prompt is gone, check if we need to resend the Claude command
1451
- const promptGone = !trustPromptPatterns.some((p) => p.test(updatedContent));
1452
- if (promptGone) {
1453
- // Check if Claude is running or if we need to restart it
1454
- const claudeRunning = updatedContent.includes("Claude") ||
1455
- updatedContent.includes("claude") ||
1456
- updatedContent.includes("Assistant") ||
1457
- (prompt &&
1458
- updatedContent.includes(prompt.substring(0, Math.min(20, prompt.length))));
1459
- if (!claudeRunning && !updatedContent.includes("$")) {
1460
- await new Promise((resolve) => setTimeout(resolve, 300));
1461
- execSync(`tmux send-keys -t '${paneInfo}' '${escapedCmd}'`, { stdio: "pipe" });
1462
- execSync(`tmux send-keys -t '${paneInfo}' Enter`, {
1463
- stdio: "pipe",
1464
- });
1465
- }
1466
- break;
1467
- }
1468
- }
1469
- }
1470
- // If we see Claude is already running without prompts, we're done
1471
- if (!hasTrustPrompt &&
1472
- !hasClaudePermissionPrompt &&
1473
- (paneContent.includes("Claude") ||
1474
- paneContent.includes("Assistant"))) {
1475
- break;
1476
- }
1477
- }
1478
- catch (error) {
1479
- // Continue checking, errors are non-fatal
1480
- }
1481
- }
1482
- };
1483
- // Start monitoring for trust prompt in background
1484
- autoApproveTrust().catch((err) => { });
1485
- }
1486
- // Keep focus on the new pane
1487
- execSync(`tmux select-pane -t '${paneInfo}'`, { stdio: "pipe" });
1488
- // Save pane info
1489
- const newPane = {
1490
- id: `dmux-${Date.now()}`,
1491
- slug,
1492
- prompt: prompt || "No initial prompt",
1493
- paneId: paneInfo,
1494
- worktreePath,
1495
- agent,
1496
- };
1497
- const updatedPanes = [...panes, newPane];
1498
- await savePanes(updatedPanes);
1499
- // Switch back to the original pane (where dmux is running)
1500
- execSync(`tmux select-pane -t '${originalPaneId}'`, { stdio: "pipe" });
1501
- // Re-set the title for the dmux pane
1502
- try {
1503
- execSync(`tmux select-pane -t '${originalPaneId}' -T "dmux v${packageJson.version} - ${projectName}"`, { stdio: "pipe" });
1504
- }
1505
- catch {
1506
- // Ignore if setting title fails
1507
- }
1508
- // Clear the screen and redraw the UI
1509
- process.stdout.write("\x1b[2J\x1b[H");
1510
- // Reset the creating pane flag and refresh
1511
- setIsCreatingPane(false);
1512
- setStatusMessage("");
1513
- // Force a reload of panes to ensure UI is up to date
1514
- await loadPanes();
1515
- };
1516
- const jumpToPane = (paneId) => {
1517
- try {
1518
- // Enable pane borders to show titles (if not already enabled)
1519
- try {
1520
- execSync(`tmux set-option -g pane-border-status top`, { stdio: "pipe" });
1521
- }
1522
- catch {
1523
- // Ignore if already set or fails
1524
- }
1525
- execSync(`tmux select-pane -t '${paneId}'`, { stdio: "pipe" });
1526
- // Clear screen after jump to remove artifacts
1527
- clearScreen();
1528
- setStatusMessage("Jumped to pane");
1529
- setTimeout(() => setStatusMessage(""), 2000);
1530
- }
1531
- catch {
1532
- setStatusMessage("Failed to jump - pane may be closed");
1533
- setTimeout(() => setStatusMessage(""), 2000);
1534
- }
1535
- };
1536
- const runCommand = async (type, pane) => {
1537
- if (!pane.worktreePath) {
1538
- setStatusMessage("No worktree path for this pane");
1539
- setTimeout(() => setStatusMessage(""), 2000);
1540
- return;
1541
- }
1542
- const command = type === "test" ? projectSettings.testCommand : projectSettings.devCommand;
1543
- const isFirstRun = type === "test"
1544
- ? !projectSettings.firstTestRun
1545
- : !projectSettings.firstDevRun;
1546
- if (!command) {
1547
- // No command configured, prompt user
1548
- setShowCommandPrompt(type);
1549
- return;
1550
- }
1551
- // Check if this is the first run and offer to copy non-git files
1552
- if (isFirstRun) {
1553
- // Show file copy prompt and wait for response
1554
- setShowFileCopyPrompt(true);
1555
- setCurrentCommandType(type);
1556
- setStatusMessage(`First time running ${type} command...`);
1557
- // Return here - the actual command will be run after user responds to prompt
1558
- return;
1559
- }
1560
- try {
1561
- setRunningCommand(true);
1562
- setStatusMessage(`Starting ${type} in background window...`);
1563
- // Kill existing window if present
1564
- const existingWindowId = type === "test" ? pane.testWindowId : pane.devWindowId;
1565
- if (existingWindowId) {
1566
- try {
1567
- execSync(`tmux kill-window -t '${existingWindowId}'`, {
1568
- stdio: "pipe",
1569
- });
1570
- }
1571
- catch { }
1572
- }
1573
- // Create a new background window for the command
1574
- const windowName = `${pane.slug}-${type}`;
1575
- const windowId = execSync(`tmux new-window -d -n '${windowName}' -P -F '#{window_id}'`, { encoding: "utf-8", stdio: "pipe" }).trim();
1576
- // Create a log file to capture output
1577
- const logFile = `/tmp/dmux-${pane.id}-${type}.log`;
1578
- // Build the command with output capture
1579
- const fullCommand = type === "test"
1580
- ? `cd "${pane.worktreePath}" && ${command} 2>&1 | tee ${logFile}`
1581
- : `cd "${pane.worktreePath}" && ${command} 2>&1 | tee ${logFile}`;
1582
- // Send the command to the new window
1583
- execSync(`tmux send-keys -t '${windowId}' '${fullCommand.replace(/'/g, "'\\''")}'`, { stdio: "pipe" });
1584
- execSync(`tmux send-keys -t '${windowId}' Enter`, { stdio: "pipe" });
1585
- // Update pane with window info
1586
- const updatedPane = {
1587
- ...pane,
1588
- [type === "test" ? "testWindowId" : "devWindowId"]: windowId,
1589
- [type === "test" ? "testStatus" : "devStatus"]: "running",
1590
- };
1591
- const updatedPanes = panes.map((p) => p.id === pane.id ? updatedPane : p);
1592
- await savePanes(updatedPanes);
1593
- // Start monitoring the output
1594
- if (type === "test") {
1595
- // For tests, monitor for completion
1596
- setTimeout(() => monitorTestOutput(pane.id, logFile), 2000);
1597
- }
1598
- else {
1599
- // For dev, monitor for server URL
1600
- setTimeout(() => monitorDevOutput(pane.id, logFile), 2000);
1601
- }
1602
- setRunningCommand(false);
1603
- setStatusMessage(`${type === "test" ? "Test" : "Dev server"} started in background`);
1604
- setTimeout(() => setStatusMessage(""), 3000);
1605
- }
1606
- catch (error) {
1607
- setRunningCommand(false);
1608
- setStatusMessage(`Failed to run ${type} command`);
1609
- setTimeout(() => setStatusMessage(""), 3000);
1610
- }
1611
- };
492
+ // jumpToPane and runCommand functions removed - now handled by action system and pane runner
1612
493
  // Update handling moved to useAutoUpdater
1613
- // Helper function to clear screen artifacts
1614
- const clearScreen = () => {
1615
- // CRITICAL: Force Ink to re-render FIRST, before clearing
1616
- // This prevents blank screen by ensuring React starts rendering immediately
1617
- setForceRepaintTrigger((prev) => prev + 1);
1618
- // Multiple clearing strategies to prevent artifacts
1619
- // 1. Clear screen with ANSI codes
1620
- process.stdout.write("\x1b[2J\x1b[H");
1621
- // 2. Clear tmux history
1622
- try {
1623
- execSync("tmux clear-history", { stdio: "pipe" });
1624
- }
1625
- catch { }
1626
- // 3. Force tmux to refresh the display
1627
- try {
1628
- execSync("tmux refresh-client", { stdio: "pipe" });
1629
- }
1630
- catch { }
1631
- };
494
+ // clearScreen function removed - no longer used (was only used by removed jumpToPane function)
1632
495
  // Cleanup function for exit
1633
496
  const cleanExit = () => {
1634
497
  // Clear screen before exiting Ink
@@ -1636,15 +499,16 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1636
499
  // Exit the Ink app (this cleans up the React tree)
1637
500
  exit();
1638
501
  // Give Ink a moment to clean up its rendering, then do final cleanup
1639
- setTimeout(() => {
502
+ setTimeout(async () => {
1640
503
  // Multiple aggressive clearing strategies
1641
504
  process.stdout.write("\x1b[2J\x1b[H"); // Clear screen and move cursor to home
1642
505
  process.stdout.write("\x1b[3J"); // Clear scrollback buffer
1643
506
  process.stdout.write("\x1b[0m"); // Reset all attributes
1644
507
  // Clear tmux history and pane
1645
508
  try {
1646
- execSync("tmux clear-history", { stdio: "pipe" });
1647
- execSync("tmux send-keys C-l", { stdio: "pipe" });
509
+ const tmuxService = TmuxService.getInstance();
510
+ tmuxService.clearHistorySync();
511
+ await tmuxService.sendKeys("", "C-l");
1648
512
  }
1649
513
  catch { }
1650
514
  // One more final clear
@@ -1655,250 +519,53 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1655
519
  process.exit(0);
1656
520
  }, 100);
1657
521
  };
1658
- useInput(async (input, key) => {
1659
- const logService = LogService.getInstance();
1660
- // Log all input for debugging (only first 50 chars to avoid spam)
1661
- const inputPreview = input.length > 50 ? input.substring(0, 50) + "..." : input;
1662
- logService.debug(`Input: "${inputPreview}"`, "InputDebug");
1663
- // Ignore input temporarily after popup operations (prevents buffered keys from being processed)
1664
- if (ignoreInput) {
1665
- return;
1666
- }
1667
- // Handle Ctrl+C for quit confirmation (must be first, before any other checks)
1668
- if (key.ctrl && input === "c") {
1669
- if (quitConfirmMode) {
1670
- // Second Ctrl+C - actually quit
1671
- cleanExit();
1672
- }
1673
- else {
1674
- // First Ctrl+C - show confirmation
1675
- setQuitConfirmMode(true);
1676
- // Reset after 3 seconds if user doesn't press Ctrl+C again
1677
- setTimeout(() => {
1678
- setQuitConfirmMode(false);
1679
- }, 3000);
1680
- }
1681
- return;
1682
- }
1683
- if (isCreatingPane || runningCommand || isUpdating || isLoading) {
1684
- // Disable input while performing operations or loading
1685
- return;
1686
- }
1687
- // Handle quit confirm mode - ESC cancels it
1688
- if (quitConfirmMode) {
1689
- if (key.escape) {
1690
- setQuitConfirmMode(false);
1691
- return;
1692
- }
1693
- // Allow other inputs to continue (don't return early)
1694
- }
1695
- if (showFileCopyPrompt) {
1696
- if (input === "y" || input === "Y") {
1697
- setShowFileCopyPrompt(false);
1698
- const selectedPane = panes[selectedIndex];
1699
- if (selectedPane && selectedPane.worktreePath && currentCommandType) {
1700
- await copyNonGitFiles(selectedPane.worktreePath);
1701
- // Mark as not first run and continue with command
1702
- const newSettings = {
1703
- ...projectSettings,
1704
- [currentCommandType === "test" ? "firstTestRun" : "firstDevRun"]: true,
1705
- };
1706
- await saveSettings(newSettings);
1707
- // Now run the actual command
1708
- await runCommandInternal(currentCommandType, selectedPane);
1709
- }
1710
- setCurrentCommandType(null);
1711
- }
1712
- else if (input === "n" || input === "N" || key.escape) {
1713
- setShowFileCopyPrompt(false);
1714
- const selectedPane = panes[selectedIndex];
1715
- if (selectedPane && currentCommandType) {
1716
- // Mark as not first run and continue without copying
1717
- const newSettings = {
1718
- ...projectSettings,
1719
- [currentCommandType === "test" ? "firstTestRun" : "firstDevRun"]: true,
1720
- };
1721
- await saveSettings(newSettings);
1722
- // Now run the actual command
1723
- await runCommandInternal(currentCommandType, selectedPane);
1724
- }
1725
- setCurrentCommandType(null);
1726
- }
1727
- return;
1728
- }
1729
- if (showCommandPrompt) {
1730
- if (key.escape) {
1731
- setShowCommandPrompt(null);
1732
- setCommandInput("");
1733
- }
1734
- else if (key.return) {
1735
- if (commandInput.trim() === "") {
1736
- // If empty, suggest a default command based on package manager
1737
- const suggested = await suggestCommand(showCommandPrompt);
1738
- if (suggested) {
1739
- setCommandInput(suggested);
1740
- }
1741
- }
1742
- else {
1743
- // User provided manual command
1744
- const newSettings = {
1745
- ...projectSettings,
1746
- [showCommandPrompt === "test" ? "testCommand" : "devCommand"]: commandInput.trim(),
1747
- };
1748
- await saveSettings(newSettings);
1749
- const selectedPane = panes[selectedIndex];
1750
- if (selectedPane) {
1751
- // Check if first run
1752
- const isFirstRun = showCommandPrompt === "test"
1753
- ? !projectSettings.firstTestRun
1754
- : !projectSettings.firstDevRun;
1755
- if (isFirstRun) {
1756
- setCurrentCommandType(showCommandPrompt);
1757
- setShowCommandPrompt(null);
1758
- setShowFileCopyPrompt(true);
1759
- }
1760
- else {
1761
- await runCommandInternal(showCommandPrompt, selectedPane);
1762
- setShowCommandPrompt(null);
1763
- setCommandInput("");
1764
- }
1765
- }
1766
- else {
1767
- setShowCommandPrompt(null);
1768
- setCommandInput("");
1769
- }
1770
- }
1771
- }
1772
- return;
1773
- }
1774
- // Handle directional navigation with spatial awareness based on card grid layout
1775
- if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
1776
- let targetIndex = null;
1777
- if (key.upArrow) {
1778
- targetIndex = findCardInDirection(selectedIndex, "up");
1779
- }
1780
- else if (key.downArrow) {
1781
- targetIndex = findCardInDirection(selectedIndex, "down");
1782
- }
1783
- else if (key.leftArrow) {
1784
- targetIndex = findCardInDirection(selectedIndex, "left");
1785
- }
1786
- else if (key.rightArrow) {
1787
- targetIndex = findCardInDirection(selectedIndex, "right");
1788
- }
1789
- if (targetIndex !== null) {
1790
- setSelectedIndex(targetIndex);
1791
- }
1792
- return;
1793
- }
1794
- if (input === "m" && selectedIndex < panes.length) {
1795
- // Open kebab menu popup for selected pane
1796
- await launchKebabMenuPopup(selectedIndex);
1797
- }
1798
- else if (input === "s") {
1799
- // Open settings popup
1800
- await launchSettingsPopup();
1801
- }
1802
- else if (input === "l") {
1803
- // Open logs popup
1804
- await launchLogsPopup();
1805
- }
1806
- else if (input === "?") {
1807
- // Open keyboard shortcuts popup
1808
- await launchShortcutsPopup();
1809
- }
1810
- else if (input === "L" && controlPaneId) {
1811
- // Reset layout to sidebar configuration (Shift+L)
1812
- enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH);
1813
- setStatusMessage("Layout reset");
1814
- setTimeout(() => setStatusMessage(""), 2000);
1815
- }
1816
- else if (input === "q") {
1817
- cleanExit();
1818
- }
1819
- else if (input === "r" && server) {
1820
- // Handle remote tunnel
1821
- if (tunnelUrl) {
1822
- // Tunnel exists - open popup with QR code
1823
- await launchRemotePopup();
1824
- }
1825
- else if (!tunnelCreating) {
1826
- // Start tunnel creation
1827
- setTunnelCreating(true);
1828
- (async () => {
1829
- try {
1830
- const url = await server.startTunnel();
1831
- setTunnelUrl(url);
1832
- }
1833
- catch (error) {
1834
- setStatusMessage(`Failed to create tunnel: ${error.message}`);
1835
- setTimeout(() => setStatusMessage(""), 3000);
1836
- }
1837
- finally {
1838
- setTunnelCreating(false);
1839
- }
1840
- })();
1841
- }
1842
- // If tunnelCreating is true, do nothing (already creating)
1843
- return;
1844
- }
1845
- else if (!isLoading &&
1846
- (input === "n" || (key.return && selectedIndex === panes.length))) {
1847
- // Launch popup modal for new pane
1848
- await launchNewPanePopup();
1849
- return;
1850
- }
1851
- else if (!isLoading &&
1852
- (input === "t" || (key.return && selectedIndex === panes.length + 1))) {
1853
- // Create a new terminal pane without an agent
1854
- try {
1855
- setIsCreatingPane(true);
1856
- setStatusMessage("Creating terminal pane...");
1857
- // Create a simple tmux pane split
1858
- const paneInfo = execSync(`tmux split-window -h -P -F '#{pane_id}'`, {
1859
- encoding: "utf-8",
1860
- }).trim();
1861
- // Wait for pane creation to settle
1862
- await new Promise((resolve) => setTimeout(resolve, 300));
1863
- // The shell pane will be automatically detected by the shell pane detection system
1864
- // No need to manually add it to the panes array
1865
- setIsCreatingPane(false);
1866
- setStatusMessage("Terminal pane created");
1867
- setTimeout(() => setStatusMessage(""), 2000);
1868
- // Force a reload to pick up the new shell pane
1869
- await loadPanes();
1870
- }
1871
- catch (error) {
1872
- setIsCreatingPane(false);
1873
- setStatusMessage(`Failed to create terminal pane: ${error.message}`);
1874
- setTimeout(() => setStatusMessage(""), 3000);
1875
- }
1876
- return;
1877
- }
1878
- else if (input === "j" && selectedIndex < panes.length) {
1879
- // Jump to pane (NEW: using action system)
1880
- StateManager.getInstance().setDebugMessage(`Jumping to pane: ${panes[selectedIndex].slug}`);
1881
- setTimeout(() => StateManager.getInstance().setDebugMessage(""), 2000);
1882
- actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
1883
- }
1884
- else if (input === "x" && selectedIndex < panes.length) {
1885
- // Close pane (NEW: using action system)
1886
- StateManager.getInstance().setDebugMessage(`Closing pane: ${panes[selectedIndex].slug}`);
1887
- setTimeout(() => StateManager.getInstance().setDebugMessage(""), 2000);
1888
- actionSystem.executeAction(PaneAction.CLOSE, panes[selectedIndex]);
1889
- }
1890
- else if (key.return && selectedIndex < panes.length) {
1891
- // Jump to pane (NEW: using action system)
1892
- StateManager.getInstance().setDebugMessage(`Jumping to pane: ${panes[selectedIndex].slug}`);
1893
- setTimeout(() => StateManager.getInstance().setDebugMessage(""), 2000);
1894
- actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
1895
- }
522
+ // Input handling - extracted to dedicated hook
523
+ useInputHandling({
524
+ panes,
525
+ selectedIndex,
526
+ setSelectedIndex,
527
+ isCreatingPane,
528
+ setIsCreatingPane,
529
+ runningCommand,
530
+ isUpdating,
531
+ isLoading,
532
+ ignoreInput,
533
+ quitConfirmMode,
534
+ setQuitConfirmMode,
535
+ showCommandPrompt,
536
+ setShowCommandPrompt,
537
+ commandInput,
538
+ setCommandInput,
539
+ showFileCopyPrompt,
540
+ setShowFileCopyPrompt,
541
+ currentCommandType,
542
+ setCurrentCommandType,
543
+ projectSettings,
544
+ saveSettings,
545
+ settingsManager,
546
+ tunnelUrl,
547
+ setTunnelUrl,
548
+ tunnelCreating,
549
+ setTunnelCreating,
550
+ setTunnelCopied,
551
+ popupManager,
552
+ actionSystem,
553
+ server,
554
+ controlPaneId,
555
+ setStatusMessage,
556
+ copyNonGitFiles,
557
+ runCommandInternal,
558
+ handlePaneCreationWithAgent,
559
+ loadPanes,
560
+ cleanExit,
561
+ findCardInDirection,
1896
562
  });
1897
563
  // Calculate available height for content (terminal height - footer lines - active status messages)
1898
564
  // Footer height varies based on state:
1899
565
  // - Quit confirm mode: 2 lines (marginTop + 1 text line)
1900
566
  // - Normal mode calculation:
1901
567
  // - Base: 4 lines (marginTop + logs divider + logs line + keyboard shortcuts)
568
+ // - Toast: +2 lines (toast message + marginBottom) if currentToast exists
1902
569
  // - Network section: +4 lines (divider, local IP, remote tunnel, divider) if serverPort exists
1903
570
  // - Debug info: +1 line if DEBUG_DMUX
1904
571
  // - Status line: +1 line if updateAvailable/currentBranch/debugMessage
@@ -1910,6 +577,17 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1910
577
  else {
1911
578
  // Base footer (logs divider + logs + shortcuts - always shown)
1912
579
  footerLines = 4; // marginTop + logs divider + logs + shortcuts
580
+ // Add toast notification (calculate wrapped lines + header)
581
+ if (currentToast) {
582
+ // Calculate how many lines the toast will take
583
+ // Toast format: "✓ message" - icon (1) + space (1) + message
584
+ const iconAndSpaceLength = 2;
585
+ const toastTextLength = iconAndSpaceLength + currentToast.message.length;
586
+ // Available width is sidebar width (40) minus padding/margins (~2)
587
+ const availableWidth = SIDEBAR_WIDTH - 2;
588
+ const wrappedLines = Math.ceil(toastTextLength / availableWidth);
589
+ footerLines += wrappedLines + 1 + 1; // wrapped lines + header line + marginBottom
590
+ }
1913
591
  // Add network section (now 2 lines for local IP + remote tunnel, plus 2 dividers)
1914
592
  if (serverPort && serverPort > 0) {
1915
593
  footerLines += 4;
@@ -1949,21 +627,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1949
627
  : actionSystem.actionState.statusType === "success"
1950
628
  ? "green"
1951
629
  : "cyan" }, actionSystem.actionState.statusMessage))),
1952
- React.createElement(FooterHelp, { show: !showCommandPrompt, showRemoteKey: !!server, quitConfirmMode: quitConfirmMode, hasSidebarLayout: !!controlPaneId, serverPort: serverPort, unreadErrorCount: unreadErrorCount, unreadWarningCount: unreadWarningCount, localIp: localIp, tunnelUrl: tunnelUrl, tunnelCreating: tunnelCreating, tunnelCopied: tunnelCopied, tunnelSpinner: (() => {
1953
- const spinnerFrames = [
1954
- "⠋",
1955
- "⠙",
1956
- "⠹",
1957
- "⠸",
1958
- "⠼",
1959
- "⠴",
1960
- "⠦",
1961
- "⠧",
1962
- "⠇",
1963
- "⠏",
1964
- ];
1965
- return spinnerFrames[tunnelSpinnerFrame];
1966
- })(), gridInfo: (() => {
630
+ React.createElement(FooterHelp, { show: !showCommandPrompt, showRemoteKey: !!server, quitConfirmMode: quitConfirmMode, hasSidebarLayout: !!controlPaneId, serverPort: serverPort, unreadErrorCount: unreadErrorCount, unreadWarningCount: unreadWarningCount, currentToast: currentToast, toastQueueLength: toastQueueLength, toastQueuePosition: toastQueuePosition, localIp: localIp, tunnelUrl: tunnelUrl, tunnelCreating: tunnelCreating, tunnelCopied: tunnelCopied, tunnelSpinner: tunnelState.getSpinnerChar(), gridInfo: (() => {
1967
631
  if (!process.env.DEBUG_DMUX)
1968
632
  return undefined;
1969
633
  const cols = Math.max(1, Math.floor(terminalWidth / 37));
@@ -1975,8 +639,8 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
1975
639
  updateAvailable && updateInfo && (React.createElement(Text, { color: "red", bold: true },
1976
640
  "Update available: npm i -g dmux@latest",
1977
641
  " ")),
1978
- currentBranch && (React.createElement(Text, { color: "magenta", bold: true },
1979
- "branch: ",
642
+ currentBranch && (React.createElement(Text, { color: "magenta" },
643
+ "dev: ",
1980
644
  currentBranch)),
1981
645
  debugMessage && React.createElement(Text, { dimColor: true },
1982
646
  " \u2022 ",