dmux 5.3.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +2 -1
  2. package/dist/DmuxApp.d.ts.map +1 -1
  3. package/dist/DmuxApp.js +226 -51
  4. package/dist/DmuxApp.js.map +1 -1
  5. package/dist/FileBrowserApp.d.ts +4 -0
  6. package/dist/FileBrowserApp.d.ts.map +1 -0
  7. package/dist/FileBrowserApp.js +693 -0
  8. package/dist/FileBrowserApp.js.map +1 -0
  9. package/dist/actions/implementations/closeAction.d.ts.map +1 -1
  10. package/dist/actions/implementations/closeAction.js +47 -5
  11. package/dist/actions/implementations/closeAction.js.map +1 -1
  12. package/dist/actions/implementations/mergeAction.d.ts.map +1 -1
  13. package/dist/actions/implementations/mergeAction.js +51 -16
  14. package/dist/actions/implementations/mergeAction.js.map +1 -1
  15. package/dist/actions/implementations/viewAction.d.ts.map +1 -1
  16. package/dist/actions/implementations/viewAction.js +7 -0
  17. package/dist/actions/implementations/viewAction.js.map +1 -1
  18. package/dist/actions/index.d.ts.map +1 -1
  19. package/dist/actions/index.js +24 -0
  20. package/dist/actions/index.js.map +1 -1
  21. package/dist/actions/merge/multiMergeOrchestrator.js +1 -1
  22. package/dist/actions/merge/multiMergeOrchestrator.js.map +1 -1
  23. package/dist/actions/types.d.ts +19 -4
  24. package/dist/actions/types.d.ts.map +1 -1
  25. package/dist/actions/types.js +91 -0
  26. package/dist/actions/types.js.map +1 -1
  27. package/dist/components/indicators/Spinner.d.ts +2 -0
  28. package/dist/components/indicators/Spinner.d.ts.map +1 -1
  29. package/dist/components/indicators/Spinner.js +4 -4
  30. package/dist/components/indicators/Spinner.js.map +1 -1
  31. package/dist/components/panes/KebabMenu.d.ts +2 -2
  32. package/dist/components/panes/KebabMenu.d.ts.map +1 -1
  33. package/dist/components/panes/KebabMenu.js +9 -4
  34. package/dist/components/panes/KebabMenu.js.map +1 -1
  35. package/dist/components/panes/PaneCard.d.ts.map +1 -1
  36. package/dist/components/panes/PaneCard.js +20 -4
  37. package/dist/components/panes/PaneCard.js.map +1 -1
  38. package/dist/components/panes/PanesGrid.d.ts +1 -0
  39. package/dist/components/panes/PanesGrid.d.ts.map +1 -1
  40. package/dist/components/panes/PanesGrid.js +11 -3
  41. package/dist/components/panes/PanesGrid.js.map +1 -1
  42. package/dist/components/popups/agentChoicePopup.js +29 -24
  43. package/dist/components/popups/agentChoicePopup.js.map +1 -1
  44. package/dist/components/popups/agentChoiceSelection.d.ts +8 -0
  45. package/dist/components/popups/agentChoiceSelection.d.ts.map +1 -0
  46. package/dist/components/popups/agentChoiceSelection.js +14 -0
  47. package/dist/components/popups/agentChoiceSelection.js.map +1 -0
  48. package/dist/components/popups/kebabMenuPopup.js +9 -4
  49. package/dist/components/popups/kebabMenuPopup.js.map +1 -1
  50. package/dist/components/popups/notificationSoundsPopup.d.ts +25 -0
  51. package/dist/components/popups/notificationSoundsPopup.d.ts.map +1 -0
  52. package/dist/components/popups/notificationSoundsPopup.js +165 -0
  53. package/dist/components/popups/notificationSoundsPopup.js.map +1 -0
  54. package/dist/components/popups/settingsPopup.js +361 -26
  55. package/dist/components/popups/settingsPopup.js.map +1 -1
  56. package/dist/components/popups/shortcutsPopup.js +11 -5
  57. package/dist/components/popups/shortcutsPopup.js.map +1 -1
  58. package/dist/constants/layout.d.ts +9 -0
  59. package/dist/constants/layout.d.ts.map +1 -0
  60. package/dist/constants/layout.js +9 -0
  61. package/dist/constants/layout.js.map +1 -0
  62. package/dist/hooks/useActionSystem.d.ts +9 -5
  63. package/dist/hooks/useActionSystem.d.ts.map +1 -1
  64. package/dist/hooks/useActionSystem.js +21 -19
  65. package/dist/hooks/useActionSystem.js.map +1 -1
  66. package/dist/hooks/useInputHandling.d.ts +1 -0
  67. package/dist/hooks/useInputHandling.d.ts.map +1 -1
  68. package/dist/hooks/useInputHandling.js +499 -79
  69. package/dist/hooks/useInputHandling.js.map +1 -1
  70. package/dist/hooks/usePaneCreation.d.ts +4 -2
  71. package/dist/hooks/usePaneCreation.d.ts.map +1 -1
  72. package/dist/hooks/usePaneCreation.js +6 -0
  73. package/dist/hooks/usePaneCreation.js.map +1 -1
  74. package/dist/hooks/usePaneLoading.d.ts +1 -0
  75. package/dist/hooks/usePaneLoading.d.ts.map +1 -1
  76. package/dist/hooks/usePaneLoading.js +10 -7
  77. package/dist/hooks/usePaneLoading.js.map +1 -1
  78. package/dist/hooks/usePaneSync.js +2 -2
  79. package/dist/hooks/usePaneSync.js.map +1 -1
  80. package/dist/hooks/usePanes.d.ts.map +1 -1
  81. package/dist/hooks/usePanes.js +18 -4
  82. package/dist/hooks/usePanes.js.map +1 -1
  83. package/dist/hooks/useProjectActivity.d.ts +7 -0
  84. package/dist/hooks/useProjectActivity.d.ts.map +1 -0
  85. package/dist/hooks/useProjectActivity.js +79 -0
  86. package/dist/hooks/useProjectActivity.js.map +1 -0
  87. package/dist/hooks/useServices.d.ts +3 -0
  88. package/dist/hooks/useServices.d.ts.map +1 -1
  89. package/dist/hooks/useServices.js +4 -0
  90. package/dist/hooks/useServices.js.map +1 -1
  91. package/dist/index.js +59 -15
  92. package/dist/index.js.map +1 -1
  93. package/dist/layout/LayoutCalculator.d.ts.map +1 -1
  94. package/dist/layout/LayoutCalculator.js +4 -1
  95. package/dist/layout/LayoutCalculator.js.map +1 -1
  96. package/dist/services/DmuxAttentionService.d.ts +33 -0
  97. package/dist/services/DmuxAttentionService.d.ts.map +1 -0
  98. package/dist/services/DmuxAttentionService.js +172 -0
  99. package/dist/services/DmuxAttentionService.js.map +1 -0
  100. package/dist/services/DmuxFocusService.d.ts +58 -0
  101. package/dist/services/DmuxFocusService.d.ts.map +1 -0
  102. package/dist/services/DmuxFocusService.js +671 -0
  103. package/dist/services/DmuxFocusService.js.map +1 -0
  104. package/dist/services/PaneAnalyzer.d.ts +11 -2
  105. package/dist/services/PaneAnalyzer.d.ts.map +1 -1
  106. package/dist/services/PaneAnalyzer.js +88 -22
  107. package/dist/services/PaneAnalyzer.js.map +1 -1
  108. package/dist/services/PopupManager.d.ts +31 -14
  109. package/dist/services/PopupManager.d.ts.map +1 -1
  110. package/dist/services/PopupManager.js +147 -68
  111. package/dist/services/PopupManager.js.map +1 -1
  112. package/dist/services/StatusDetector.d.ts +14 -0
  113. package/dist/services/StatusDetector.d.ts.map +1 -1
  114. package/dist/services/StatusDetector.js +60 -12
  115. package/dist/services/StatusDetector.js.map +1 -1
  116. package/dist/services/TmuxHookManager.d.ts.map +1 -1
  117. package/dist/services/TmuxHookManager.js +4 -2
  118. package/dist/services/TmuxHookManager.js.map +1 -1
  119. package/dist/services/TmuxService.d.ts +37 -2
  120. package/dist/services/TmuxService.d.ts.map +1 -1
  121. package/dist/services/TmuxService.js +138 -16
  122. package/dist/services/TmuxService.js.map +1 -1
  123. package/dist/types/activity.d.ts +4 -0
  124. package/dist/types/activity.d.ts.map +1 -0
  125. package/dist/types/activity.js +2 -0
  126. package/dist/types/activity.js.map +1 -0
  127. package/dist/types.d.ts +18 -1
  128. package/dist/types.d.ts.map +1 -1
  129. package/dist/utils/attachAgent.d.ts +4 -0
  130. package/dist/utils/attachAgent.d.ts.map +1 -1
  131. package/dist/utils/attachAgent.js +18 -7
  132. package/dist/utils/attachAgent.js.map +1 -1
  133. package/dist/utils/controlPaneRecovery.d.ts +2 -0
  134. package/dist/utils/controlPaneRecovery.d.ts.map +1 -0
  135. package/dist/utils/controlPaneRecovery.js +156 -0
  136. package/dist/utils/controlPaneRecovery.js.map +1 -0
  137. package/dist/utils/devWatchExit.d.ts +2 -0
  138. package/dist/utils/devWatchExit.d.ts.map +1 -0
  139. package/dist/utils/devWatchExit.js +10 -0
  140. package/dist/utils/devWatchExit.js.map +1 -0
  141. package/dist/utils/dmuxCommand.d.ts +3 -0
  142. package/dist/utils/dmuxCommand.d.ts.map +1 -0
  143. package/dist/utils/dmuxCommand.js +18 -0
  144. package/dist/utils/dmuxCommand.js.map +1 -0
  145. package/dist/utils/fileBrowser.d.ts +61 -0
  146. package/dist/utils/fileBrowser.d.ts.map +1 -0
  147. package/dist/utils/fileBrowser.js +567 -0
  148. package/dist/utils/fileBrowser.js.map +1 -0
  149. package/dist/utils/focusDetection.d.ts +38 -0
  150. package/dist/utils/focusDetection.d.ts.map +1 -0
  151. package/dist/utils/focusDetection.js +57 -0
  152. package/dist/utils/focusDetection.js.map +1 -0
  153. package/dist/utils/generated-agents-doc.d.ts +1 -1
  154. package/dist/utils/generated-agents-doc.js +1 -1
  155. package/dist/utils/git.d.ts +4 -0
  156. package/dist/utils/git.d.ts.map +1 -1
  157. package/dist/utils/git.js +15 -0
  158. package/dist/utils/git.js.map +1 -1
  159. package/dist/utils/layoutManager.d.ts +5 -1
  160. package/dist/utils/layoutManager.d.ts.map +1 -1
  161. package/dist/utils/layoutManager.js +103 -26
  162. package/dist/utils/layoutManager.js.map +1 -1
  163. package/dist/utils/mergeTargets.d.ts +17 -0
  164. package/dist/utils/mergeTargets.d.ts.map +1 -0
  165. package/dist/utils/mergeTargets.js +132 -0
  166. package/dist/utils/mergeTargets.js.map +1 -0
  167. package/dist/utils/mergeValidation.d.ts.map +1 -1
  168. package/dist/utils/mergeValidation.js +12 -5
  169. package/dist/utils/mergeValidation.js.map +1 -1
  170. package/dist/utils/notificationSoundPreview.d.ts +10 -0
  171. package/dist/utils/notificationSoundPreview.d.ts.map +1 -0
  172. package/dist/utils/notificationSoundPreview.js +54 -0
  173. package/dist/utils/notificationSoundPreview.js.map +1 -0
  174. package/dist/utils/notificationSounds.d.ts +17 -0
  175. package/dist/utils/notificationSounds.d.ts.map +1 -0
  176. package/dist/utils/notificationSounds.js +123 -0
  177. package/dist/utils/notificationSounds.js.map +1 -0
  178. package/dist/utils/paneAttentionHeuristics.d.ts +4 -0
  179. package/dist/utils/paneAttentionHeuristics.d.ts.map +1 -0
  180. package/dist/utils/paneAttentionHeuristics.js +135 -0
  181. package/dist/utils/paneAttentionHeuristics.js.map +1 -0
  182. package/dist/utils/paneCreation.d.ts +3 -1
  183. package/dist/utils/paneCreation.d.ts.map +1 -1
  184. package/dist/utils/paneCreation.js +23 -5
  185. package/dist/utils/paneCreation.js.map +1 -1
  186. package/dist/utils/paneVisibility.d.ts +12 -0
  187. package/dist/utils/paneVisibility.d.ts.map +1 -0
  188. package/dist/utils/paneVisibility.js +60 -0
  189. package/dist/utils/paneVisibility.js.map +1 -0
  190. package/dist/utils/processShutdown.d.ts +4 -0
  191. package/dist/utils/processShutdown.d.ts.map +1 -0
  192. package/dist/utils/processShutdown.js +27 -0
  193. package/dist/utils/processShutdown.js.map +1 -0
  194. package/dist/utils/promptStore.d.ts.map +1 -1
  195. package/dist/utils/promptStore.js +6 -0
  196. package/dist/utils/promptStore.js.map +1 -1
  197. package/dist/utils/reopenWorktree.d.ts.map +1 -1
  198. package/dist/utils/reopenWorktree.js +8 -0
  199. package/dist/utils/reopenWorktree.js.map +1 -1
  200. package/dist/utils/runtimePaths.d.ts +1 -0
  201. package/dist/utils/runtimePaths.d.ts.map +1 -1
  202. package/dist/utils/runtimePaths.js +3 -0
  203. package/dist/utils/runtimePaths.js.map +1 -1
  204. package/dist/utils/settingsManager.d.ts +3 -0
  205. package/dist/utils/settingsManager.d.ts.map +1 -1
  206. package/dist/utils/settingsManager.js +203 -11
  207. package/dist/utils/settingsManager.js.map +1 -1
  208. package/dist/utils/tmux.d.ts +5 -1
  209. package/dist/utils/tmux.d.ts.map +1 -1
  210. package/dist/utils/tmux.js +23 -5
  211. package/dist/utils/tmux.js.map +1 -1
  212. package/dist/utils/tmuxConfigOnboarding.js +1 -1
  213. package/dist/utils/tmuxConfigOnboarding.js.map +1 -1
  214. package/dist/utils/tmuxHookCommands.d.ts +14 -0
  215. package/dist/utils/tmuxHookCommands.d.ts.map +1 -0
  216. package/dist/utils/tmuxHookCommands.js +30 -0
  217. package/dist/utils/tmuxHookCommands.js.map +1 -0
  218. package/dist/utils/tmuxRuntimeCompatibility.d.ts +11 -0
  219. package/dist/utils/tmuxRuntimeCompatibility.d.ts.map +1 -0
  220. package/dist/utils/tmuxRuntimeCompatibility.js +71 -0
  221. package/dist/utils/tmuxRuntimeCompatibility.js.map +1 -0
  222. package/dist/utils/worktreeMetadata.d.ts +9 -0
  223. package/dist/utils/worktreeMetadata.d.ts.map +1 -0
  224. package/dist/utils/worktreeMetadata.js +60 -0
  225. package/dist/utils/worktreeMetadata.js.map +1 -0
  226. package/dist/workers/PaneWorker.js +64 -128
  227. package/dist/workers/PaneWorker.js.map +1 -1
  228. package/dist/workers/WorkerMessages.d.ts +4 -1
  229. package/dist/workers/WorkerMessages.d.ts.map +1 -1
  230. package/dist/workers/WorkerMessages.js.map +1 -1
  231. package/native/macos/dmux-helper-Info.plist +30 -0
  232. package/native/macos/dmux-helper-icon.png +0 -0
  233. package/native/macos/dmux-helper.swift +831 -0
  234. package/native/macos/sounds/dmux-braam.caf +0 -0
  235. package/native/macos/sounds/dmux-brass.caf +0 -0
  236. package/native/macos/sounds/dmux-ding-bell.caf +0 -0
  237. package/native/macos/sounds/dmux-future.caf +0 -0
  238. package/native/macos/sounds/dmux-harp.caf +0 -0
  239. package/native/macos/sounds/dmux-quiet-bells.caf +0 -0
  240. package/native/macos/sounds/dmux-sonar.caf +0 -0
  241. package/native/macos/sounds/dmux-success.caf +0 -0
  242. package/native/macos/sounds/dmux-triumphant-trumpet.caf +0 -0
  243. package/native/macos/sounds/dmux-war-horn.caf +0 -0
  244. package/package.json +3 -1
@@ -1,21 +1,52 @@
1
+ import { useEffect, useRef } from "react";
2
+ import path from "path";
1
3
  import { useInput } from "ink";
2
4
  import { StateManager } from "../shared/StateManager.js";
3
5
  import { TmuxService } from "../services/TmuxService.js";
4
6
  import { STATUS_MESSAGE_DURATION_SHORT, STATUS_MESSAGE_DURATION_LONG, ANIMATION_DELAY, } from "../constants/timing.js";
5
- import { PaneAction } from "../actions/index.js";
7
+ import { isPaneAction, PaneAction, TOGGLE_PANE_VISIBILITY_ACTION, } from "../actions/index.js";
6
8
  import { getMainBranch, getOrphanedWorktrees } from "../utils/git.js";
7
9
  import { enforceControlPaneSize } from "../utils/tmux.js";
8
10
  import { SIDEBAR_WIDTH } from "../utils/layoutManager.js";
9
11
  import { suggestCommand } from "../utils/commands.js";
10
- import { getPaneProjectRoot } from "../utils/paneProject.js";
12
+ import { getPaneProjectName, getPaneProjectRoot } from "../utils/paneProject.js";
11
13
  import { getProjectActionByIndex, } from "../utils/projectActions.js";
12
14
  import { createShellPane, getNextDmuxId } from "../utils/shellPaneDetection.js";
15
+ import { getBulkVisibilityAction, getProjectVisibilityAction, partitionPanesByProject, } from "../utils/paneVisibility.js";
16
+ import { buildFilesOnlyCommand } from "../utils/dmuxCommand.js";
13
17
  /**
14
18
  * Hook that handles all keyboard input for the TUI
15
19
  * Extracted from DmuxApp.tsx to reduce component complexity
16
20
  */
17
21
  export function useInputHandling(params) {
18
- const { panes, selectedIndex, setSelectedIndex, isCreatingPane, setIsCreatingPane, runningCommand, isUpdating, isLoading, ignoreInput, isDevMode, quitConfirmMode, setQuitConfirmMode, showCommandPrompt, setShowCommandPrompt, commandInput, setCommandInput, showFileCopyPrompt, setShowFileCopyPrompt, currentCommandType, setCurrentCommandType, projectSettings, saveSettings, settingsManager, popupManager, actionSystem, controlPaneId, setStatusMessage, copyNonGitFiles, runCommandInternal, handlePaneCreationWithAgent, handleReopenWorktree, setDevSourceFromPane, savePanes, loadPanes, cleanExit, availableAgents, panesFile, projectRoot, projectActionItems, findCardInDirection, } = params;
22
+ const { panes, selectedIndex, setSelectedIndex, isCreatingPane, setIsCreatingPane, runningCommand, isUpdating, isLoading, ignoreInput, isDevMode, quitConfirmMode, setQuitConfirmMode, showCommandPrompt, setShowCommandPrompt, commandInput, setCommandInput, showFileCopyPrompt, setShowFileCopyPrompt, currentCommandType, setCurrentCommandType, projectSettings, saveSettings, settingsManager, popupManager, actionSystem, controlPaneId, setStatusMessage, copyNonGitFiles, runCommandInternal, handlePaneCreationWithAgent, handleCreateChildWorktree, handleReopenWorktree, setDevSourceFromPane, savePanes, loadPanes, cleanExit, availableAgents, panesFile, projectRoot, projectActionItems, findCardInDirection, } = params;
23
+ const layoutRefreshDebounceRef = useRef(null);
24
+ useEffect(() => {
25
+ return () => {
26
+ if (layoutRefreshDebounceRef.current) {
27
+ clearTimeout(layoutRefreshDebounceRef.current);
28
+ layoutRefreshDebounceRef.current = null;
29
+ }
30
+ };
31
+ }, []);
32
+ const queueLayoutRefresh = () => {
33
+ if (!controlPaneId) {
34
+ return;
35
+ }
36
+ if (layoutRefreshDebounceRef.current) {
37
+ clearTimeout(layoutRefreshDebounceRef.current);
38
+ }
39
+ layoutRefreshDebounceRef.current = setTimeout(async () => {
40
+ layoutRefreshDebounceRef.current = null;
41
+ try {
42
+ await enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH, { forceLayout: true });
43
+ }
44
+ catch (error) {
45
+ setStatusMessage(`Setting saved but layout refresh failed: ${error?.message || String(error)}`);
46
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
47
+ }
48
+ }, 250);
49
+ };
19
50
  const handleCreateAgentPane = async (targetProjectRoot) => {
20
51
  const promptValue = await popupManager.launchNewPanePopup(targetProjectRoot);
21
52
  if (promptValue) {
@@ -46,13 +77,105 @@ export function useInputHandling(params) {
46
77
  setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
47
78
  }
48
79
  };
80
+ const openTerminalInWorktree = async (selectedPane) => {
81
+ if (!selectedPane.worktreePath) {
82
+ setStatusMessage("Cannot open terminal: this pane has no worktree");
83
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
84
+ return;
85
+ }
86
+ const targetProjectRoot = getPaneProjectRoot(selectedPane, projectRoot);
87
+ try {
88
+ setIsCreatingPane(true);
89
+ setStatusMessage(`Opening terminal in ${selectedPane.slug}...`);
90
+ const tmuxService = TmuxService.getInstance();
91
+ const newPaneId = await tmuxService.splitPane({ cwd: selectedPane.worktreePath });
92
+ // Wait for pane creation to settle
93
+ await new Promise((resolve) => setTimeout(resolve, ANIMATION_DELAY));
94
+ const shellPane = await createShellPane(newPaneId, getNextDmuxId(panes));
95
+ shellPane.projectRoot = targetProjectRoot;
96
+ await savePanes([...panes, shellPane]);
97
+ setStatusMessage(`Opened terminal in ${selectedPane.slug}`);
98
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
99
+ // Force a reload to ensure tmux metadata and pane IDs are in sync
100
+ await loadPanes();
101
+ }
102
+ catch (error) {
103
+ setStatusMessage(`Failed to open terminal in worktree: ${error.message}`);
104
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
105
+ }
106
+ finally {
107
+ setIsCreatingPane(false);
108
+ }
109
+ };
110
+ const openFileBrowserInWorktree = async (selectedPane) => {
111
+ if (!selectedPane.worktreePath) {
112
+ setStatusMessage("Cannot open file browser: this pane has no worktree");
113
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
114
+ return;
115
+ }
116
+ const existingBrowserPane = panes.find((pane) => pane.browserPath === selectedPane.worktreePath && !pane.hidden);
117
+ if (existingBrowserPane) {
118
+ try {
119
+ await TmuxService.getInstance().selectPane(existingBrowserPane.paneId);
120
+ setStatusMessage(`File browser already open for ${selectedPane.slug}`);
121
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
122
+ }
123
+ catch (error) {
124
+ setStatusMessage(`Failed to focus file browser: ${error?.message || String(error)}`);
125
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
126
+ }
127
+ return;
128
+ }
129
+ const targetProjectRoot = getPaneProjectRoot(selectedPane, projectRoot);
130
+ const targetProjectName = path.basename(targetProjectRoot);
131
+ try {
132
+ setIsCreatingPane(true);
133
+ setStatusMessage(`Opening file browser for ${selectedPane.slug}...`);
134
+ const tmuxService = TmuxService.getInstance();
135
+ const newPaneId = await tmuxService.splitPane({
136
+ cwd: selectedPane.worktreePath,
137
+ command: buildFilesOnlyCommand(),
138
+ });
139
+ await new Promise((resolve) => setTimeout(resolve, ANIMATION_DELAY));
140
+ const slugBase = `files-${path.basename(selectedPane.worktreePath)}`;
141
+ let slug = slugBase;
142
+ let suffix = 2;
143
+ while (panes.some((pane) => pane.slug === slug)) {
144
+ slug = `${slugBase}-${suffix}`;
145
+ suffix += 1;
146
+ }
147
+ const browserPane = {
148
+ id: `dmux-${getNextDmuxId(panes)}`,
149
+ slug,
150
+ prompt: "",
151
+ paneId: newPaneId,
152
+ projectRoot: targetProjectRoot,
153
+ projectName: targetProjectName,
154
+ type: "shell",
155
+ shellType: "fb",
156
+ browserPath: selectedPane.worktreePath,
157
+ };
158
+ await tmuxService.setPaneTitle(newPaneId, slug);
159
+ await savePanes([...panes, browserPane]);
160
+ await loadPanes();
161
+ setStatusMessage(`Opened file browser for ${selectedPane.slug}`);
162
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
163
+ }
164
+ catch (error) {
165
+ setStatusMessage(`Failed to open file browser: ${error?.message || String(error)}`);
166
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
167
+ }
168
+ finally {
169
+ setIsCreatingPane(false);
170
+ }
171
+ };
49
172
  const handleCreatePaneInProject = async () => {
50
173
  const selectedAction = getProjectActionByIndex(projectActionItems, selectedIndex);
51
174
  const selectedPane = selectedIndex < panes.length ? panes[selectedIndex] : undefined;
52
175
  const defaultProjectPath = selectedPane
53
176
  ? getPaneProjectRoot(selectedPane, projectRoot)
54
177
  : (selectedAction?.projectRoot || projectRoot);
55
- const requestedProjectPath = await popupManager.launchProjectSelectPopup(defaultProjectPath);
178
+ const requestedProjectPath = await popupManager.launchProjectSelectPopup(defaultProjectPath, defaultProjectPath);
56
179
  if (!requestedProjectPath) {
57
180
  return;
58
181
  }
@@ -85,19 +208,309 @@ export function useInputHandling(params) {
85
208
  const prompt = "I would like to create or edit my dmux hooks in .dmux-hooks. Please read AGENTS.md or CLAUDE.md first, then ask me what I want to create or modify.";
86
209
  await handlePaneCreationWithAgent(prompt, hooksProjectRoot);
87
210
  };
211
+ const refreshPaneLayout = async () => {
212
+ if (!controlPaneId) {
213
+ return;
214
+ }
215
+ await enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH, {
216
+ forceLayout: true,
217
+ suppressLayoutLogs: true,
218
+ });
219
+ };
220
+ const getPaneShowTarget = async (excludedPaneId) => {
221
+ const visiblePaneId = panes.find((pane) => !pane.hidden && pane.paneId !== excludedPaneId)?.paneId;
222
+ if (visiblePaneId) {
223
+ return visiblePaneId;
224
+ }
225
+ if (controlPaneId) {
226
+ return controlPaneId;
227
+ }
228
+ try {
229
+ return await TmuxService.getInstance().getCurrentPaneId();
230
+ }
231
+ catch {
232
+ return null;
233
+ }
234
+ };
235
+ const togglePaneVisibility = async (selectedPane) => {
236
+ const tmuxService = TmuxService.getInstance();
237
+ try {
238
+ setIsCreatingPane(true);
239
+ setStatusMessage(selectedPane.hidden ? `Showing ${selectedPane.slug}...` : `Hiding ${selectedPane.slug}...`);
240
+ if (selectedPane.hidden) {
241
+ const targetPaneId = await getPaneShowTarget(selectedPane.paneId);
242
+ if (!targetPaneId) {
243
+ throw new Error("No target pane is available to show this pane");
244
+ }
245
+ await tmuxService.joinPaneToTarget(selectedPane.paneId, targetPaneId);
246
+ }
247
+ else {
248
+ await tmuxService.breakPaneToWindow(selectedPane.paneId, `dmux-hidden-${selectedPane.id}`);
249
+ }
250
+ await savePanes(panes.map((pane) => pane.id === selectedPane.id
251
+ ? { ...pane, hidden: !selectedPane.hidden }
252
+ : pane));
253
+ await refreshPaneLayout();
254
+ await loadPanes();
255
+ setStatusMessage(selectedPane.hidden
256
+ ? `Showing ${selectedPane.slug}`
257
+ : `Hid ${selectedPane.slug}`);
258
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
259
+ }
260
+ catch (error) {
261
+ setStatusMessage(`Failed to toggle pane visibility: ${error?.message || String(error)}`);
262
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
263
+ }
264
+ finally {
265
+ setIsCreatingPane(false);
266
+ }
267
+ };
268
+ const toggleOtherPanesVisibility = async (selectedPane) => {
269
+ const action = getBulkVisibilityAction(panes, selectedPane);
270
+ if (!action) {
271
+ setStatusMessage("No other panes to toggle");
272
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
273
+ return;
274
+ }
275
+ const targetPanes = panes.filter((pane) => pane.id !== selectedPane.id
276
+ && (action === "hide-others" ? !pane.hidden : pane.hidden));
277
+ if (targetPanes.length === 0) {
278
+ setStatusMessage("No other panes to toggle");
279
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
280
+ return;
281
+ }
282
+ const tmuxService = TmuxService.getInstance();
283
+ const hidden = action === "hide-others";
284
+ try {
285
+ setIsCreatingPane(true);
286
+ setStatusMessage(hidden ? "Hiding other panes..." : "Showing other panes...");
287
+ for (const pane of targetPanes) {
288
+ if (hidden) {
289
+ await tmuxService.breakPaneToWindow(pane.paneId, `dmux-hidden-${pane.id}`);
290
+ continue;
291
+ }
292
+ const targetPaneId = await getPaneShowTarget(pane.paneId);
293
+ if (!targetPaneId) {
294
+ throw new Error("No target pane is available to show hidden panes");
295
+ }
296
+ await tmuxService.joinPaneToTarget(pane.paneId, targetPaneId);
297
+ }
298
+ const targetPaneIds = new Set(targetPanes.map((pane) => pane.id));
299
+ await savePanes(panes.map((pane) => targetPaneIds.has(pane.id) ? { ...pane, hidden } : pane));
300
+ await refreshPaneLayout();
301
+ await loadPanes();
302
+ setStatusMessage(hidden
303
+ ? `Hid ${targetPanes.length} other pane${targetPanes.length === 1 ? "" : "s"}`
304
+ : `Showed ${targetPanes.length} other pane${targetPanes.length === 1 ? "" : "s"}`);
305
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
306
+ }
307
+ catch (error) {
308
+ setStatusMessage(`Failed to toggle other panes: ${error?.message || String(error)}`);
309
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
310
+ }
311
+ finally {
312
+ setIsCreatingPane(false);
313
+ }
314
+ };
315
+ const toggleProjectPanesVisibility = async (targetProjectRoot = getActiveProjectRoot()) => {
316
+ const action = getProjectVisibilityAction(panes, targetProjectRoot, projectRoot);
317
+ if (!action) {
318
+ setStatusMessage("No project panes to toggle");
319
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
320
+ return;
321
+ }
322
+ const { projectPanes, otherPanes } = partitionPanesByProject(panes, targetProjectRoot, projectRoot);
323
+ if (projectPanes.length === 0) {
324
+ setStatusMessage("No project panes to toggle");
325
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
326
+ return;
327
+ }
328
+ const projectName = getPaneProjectName(projectPanes[0], projectRoot);
329
+ const panesToShow = action === "focus-project"
330
+ ? projectPanes.filter((pane) => pane.hidden)
331
+ : panes.filter((pane) => pane.hidden);
332
+ const panesToHide = action === "focus-project"
333
+ ? otherPanes.filter((pane) => !pane.hidden)
334
+ : [];
335
+ try {
336
+ setIsCreatingPane(true);
337
+ setStatusMessage(action === "focus-project"
338
+ ? `Showing ${projectName} panes...`
339
+ : "Showing all panes...");
340
+ // Show target project panes before hiding others so we always have
341
+ // an attached pane available for tmux join targets.
342
+ for (const pane of panesToShow) {
343
+ const targetPaneId = await getPaneShowTarget(pane.paneId);
344
+ if (!targetPaneId) {
345
+ throw new Error("No target pane is available to show hidden panes");
346
+ }
347
+ await TmuxService.getInstance().joinPaneToTarget(pane.paneId, targetPaneId);
348
+ }
349
+ for (const pane of panesToHide) {
350
+ await TmuxService.getInstance().breakPaneToWindow(pane.paneId, `dmux-hidden-${pane.id}`);
351
+ }
352
+ const shownPaneIds = new Set(panesToShow.map((pane) => pane.id));
353
+ const hiddenPaneIds = new Set(panesToHide.map((pane) => pane.id));
354
+ await savePanes(panes.map((pane) => {
355
+ if (shownPaneIds.has(pane.id)) {
356
+ return { ...pane, hidden: false };
357
+ }
358
+ if (hiddenPaneIds.has(pane.id)) {
359
+ return { ...pane, hidden: true };
360
+ }
361
+ return pane;
362
+ }));
363
+ await refreshPaneLayout();
364
+ await loadPanes();
365
+ setStatusMessage(action === "focus-project"
366
+ ? panesToHide.length > 0
367
+ ? `Showing only ${projectName} panes`
368
+ : `Showed ${projectName} panes`
369
+ : "Showed all panes");
370
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
371
+ }
372
+ catch (error) {
373
+ setStatusMessage(`Failed to toggle project panes: ${error?.message || String(error)}`);
374
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
375
+ }
376
+ finally {
377
+ setIsCreatingPane(false);
378
+ }
379
+ };
88
380
  const openPaneMenu = async (pane) => {
89
- const actionId = await popupManager.launchKebabMenuPopup(pane);
381
+ const actionId = await popupManager.launchKebabMenuPopup(pane, panes);
90
382
  if (!actionId) {
91
383
  return;
92
384
  }
385
+ if (actionId === TOGGLE_PANE_VISIBILITY_ACTION) {
386
+ await togglePaneVisibility(pane);
387
+ return;
388
+ }
389
+ if (actionId === "hide-others" || actionId === "show-others") {
390
+ await toggleOtherPanesVisibility(pane);
391
+ return;
392
+ }
393
+ if (actionId === "focus-project" || actionId === "show-all") {
394
+ await toggleProjectPanesVisibility(getPaneProjectRoot(pane, projectRoot));
395
+ return;
396
+ }
93
397
  if (actionId === PaneAction.SET_SOURCE) {
94
398
  await setDevSourceFromPane(pane);
95
399
  return;
96
400
  }
401
+ if (actionId === PaneAction.ATTACH_AGENT) {
402
+ await attachAgentsToPane(pane);
403
+ return;
404
+ }
405
+ if (actionId === PaneAction.CREATE_CHILD_WORKTREE) {
406
+ await handleCreateChildWorktree(pane);
407
+ return;
408
+ }
409
+ if (actionId === PaneAction.OPEN_TERMINAL_IN_WORKTREE) {
410
+ await openTerminalInWorktree(pane);
411
+ return;
412
+ }
413
+ if (actionId === PaneAction.OPEN_FILE_BROWSER) {
414
+ await openFileBrowserInWorktree(pane);
415
+ return;
416
+ }
417
+ if (!isPaneAction(actionId)) {
418
+ setStatusMessage(`Unknown menu action: ${actionId}`);
419
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
420
+ return;
421
+ }
97
422
  await actionSystem.executeAction(actionId, pane, {
98
423
  mainBranch: getMainBranch(),
99
424
  });
100
425
  };
426
+ const attachAgentsToPane = async (selectedPane) => {
427
+ if (!selectedPane.worktreePath) {
428
+ setStatusMessage("Cannot attach agent: this pane has no worktree");
429
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
430
+ return;
431
+ }
432
+ const targetProjectRoot = getPaneProjectRoot(selectedPane, projectRoot);
433
+ // Warn if agent is actively working
434
+ if (selectedPane.agentStatus === "working") {
435
+ const confirmed = await popupManager.launchConfirmPopup("Agent Active", `Agent in "${selectedPane.slug}" is currently working. Attach another agent anyway?`, "Attach", "Cancel", targetProjectRoot);
436
+ if (!confirmed)
437
+ return;
438
+ }
439
+ let selectedAgents = [];
440
+ if (availableAgents.length === 0) {
441
+ setStatusMessage("No agents available");
442
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
443
+ return;
444
+ }
445
+ else if (availableAgents.length === 1) {
446
+ selectedAgents = [availableAgents[0]];
447
+ }
448
+ else {
449
+ const agents = await popupManager.launchAgentChoicePopup(targetProjectRoot);
450
+ if (agents === null) {
451
+ return;
452
+ }
453
+ if (agents.length === 0) {
454
+ setStatusMessage("Select at least one agent");
455
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
456
+ return;
457
+ }
458
+ selectedAgents = agents;
459
+ }
460
+ // Prompt input
461
+ const promptValue = await popupManager.launchNewPanePopup(targetProjectRoot);
462
+ if (!promptValue)
463
+ return;
464
+ try {
465
+ setIsCreatingPane(true);
466
+ setStatusMessage(selectedAgents.length > 1
467
+ ? `Attaching ${selectedAgents.length} agents...`
468
+ : "Attaching agent...");
469
+ const { attachAgentToWorktree } = await import("../utils/attachAgent.js");
470
+ const createdPanes = [];
471
+ const failedAgents = [];
472
+ for (const agent of selectedAgents) {
473
+ try {
474
+ const result = await attachAgentToWorktree({
475
+ targetPane: selectedPane,
476
+ prompt: promptValue,
477
+ agent,
478
+ existingPanes: [...panes, ...createdPanes],
479
+ sessionProjectRoot: projectRoot,
480
+ sessionConfigPath: panesFile,
481
+ });
482
+ createdPanes.push(result.pane);
483
+ }
484
+ catch {
485
+ failedAgents.push(agent);
486
+ }
487
+ }
488
+ if (createdPanes.length > 0) {
489
+ const updatedPanes = [...panes, ...createdPanes];
490
+ await savePanes(updatedPanes);
491
+ await loadPanes();
492
+ }
493
+ if (failedAgents.length === 0) {
494
+ setStatusMessage(`Attached ${createdPanes.length} agent${createdPanes.length === 1 ? "" : "s"} to ${selectedPane.slug}`);
495
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
496
+ }
497
+ else if (createdPanes.length === 0) {
498
+ setStatusMessage(`Failed to attach agents: ${failedAgents.join(", ")}`);
499
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
500
+ }
501
+ else {
502
+ setStatusMessage(`Attached ${createdPanes.length}/${selectedAgents.length} agents to ${selectedPane.slug} (${failedAgents.length} failed)`);
503
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
504
+ }
505
+ }
506
+ catch (error) {
507
+ setStatusMessage(`Failed to attach agent: ${error.message}`);
508
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
509
+ }
510
+ finally {
511
+ setIsCreatingPane(false);
512
+ }
513
+ };
101
514
  useInput(async (input, key) => {
102
515
  // Ignore input temporarily after popup operations (prevents buffered keys from being processed)
103
516
  if (ignoreInput) {
@@ -232,67 +645,19 @@ export function useInputHandling(params) {
232
645
  return;
233
646
  }
234
647
  if (input === "a" && selectedIndex < panes.length) {
235
- // Attach agent to selected pane's worktree
236
- const selectedPane = panes[selectedIndex];
237
- if (!selectedPane.worktreePath) {
238
- setStatusMessage("Cannot attach agent: this pane has no worktree");
239
- setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
240
- return;
241
- }
242
- // Warn if agent is actively working
243
- if (selectedPane.agentStatus === "working") {
244
- const confirmed = await popupManager.launchConfirmPopup("Agent Active", `Agent in "${selectedPane.slug}" is currently working. Attach another agent anyway?`, "Attach", "Cancel");
245
- if (!confirmed)
246
- return;
247
- }
248
- // Agent choice (single agent only, no A/B pairs)
249
- let chosenAgent = null;
250
- if (availableAgents.length === 0) {
251
- setStatusMessage("No agents available");
252
- setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
253
- return;
254
- }
255
- else if (availableAgents.length === 1) {
256
- chosenAgent = availableAgents[0];
257
- }
258
- else {
259
- const agents = await popupManager.launchAgentChoicePopup();
260
- if (agents && agents.length > 0) {
261
- chosenAgent = agents[0]; // Single agent for attach (no A/B)
262
- }
263
- }
264
- if (!chosenAgent)
265
- return;
266
- // Prompt input
267
- const promptValue = await popupManager.launchNewPanePopup(getPaneProjectRoot(selectedPane, projectRoot));
268
- if (!promptValue)
269
- return;
270
- // Attach agent to worktree
271
- try {
272
- setIsCreatingPane(true);
273
- setStatusMessage("Attaching agent...");
274
- const { attachAgentToWorktree } = await import("../utils/attachAgent.js");
275
- const result = await attachAgentToWorktree({
276
- targetPane: selectedPane,
277
- prompt: promptValue,
278
- agent: chosenAgent,
279
- existingPanes: panes,
280
- sessionProjectRoot: projectRoot,
281
- sessionConfigPath: panesFile,
282
- });
283
- const updatedPanes = [...panes, result.pane];
284
- await savePanes(updatedPanes);
285
- await loadPanes();
286
- setStatusMessage(`Attached ${chosenAgent} to ${selectedPane.slug}`);
287
- setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
288
- }
289
- catch (error) {
290
- setStatusMessage(`Failed to attach agent: ${error.message}`);
291
- setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
292
- }
293
- finally {
294
- setIsCreatingPane(false);
295
- }
648
+ await attachAgentsToPane(panes[selectedIndex]);
649
+ return;
650
+ }
651
+ else if (input === "b" && selectedIndex < panes.length) {
652
+ await handleCreateChildWorktree(panes[selectedIndex]);
653
+ return;
654
+ }
655
+ else if (input === "f" && selectedIndex < panes.length) {
656
+ await openFileBrowserInWorktree(panes[selectedIndex]);
657
+ return;
658
+ }
659
+ else if (input === "A" && selectedIndex < panes.length) {
660
+ await openTerminalInWorktree(panes[selectedIndex]);
296
661
  return;
297
662
  }
298
663
  else if (input === "m" && selectedIndex < panes.length) {
@@ -306,34 +671,89 @@ export function useInputHandling(params) {
306
671
  // Launch hooks popup
307
672
  await popupManager.launchHooksPopup(async () => {
308
673
  await launchHooksAuthoringSession();
309
- });
310
- });
674
+ }, getActiveProjectRoot());
675
+ }, getActiveProjectRoot());
311
676
  if (result) {
312
- settingsManager.updateSetting(result.key, result.value, result.scope);
313
- setStatusMessage(`Setting saved (${result.scope})`);
314
- setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
677
+ try {
678
+ const updates = Array.isArray(result.updates)
679
+ ? result.updates
680
+ : [result];
681
+ let savedCount = 0;
682
+ let layoutBoundsUpdated = false;
683
+ let lastScope = null;
684
+ for (const update of updates) {
685
+ if (!update
686
+ || typeof update.key !== "string"
687
+ || (update.scope !== "global" && update.scope !== "project")) {
688
+ continue;
689
+ }
690
+ settingsManager.updateSetting(update.key, update.value, update.scope);
691
+ savedCount += 1;
692
+ lastScope = update.scope;
693
+ if (update.key === "minPaneWidth" || update.key === "maxPaneWidth") {
694
+ layoutBoundsUpdated = true;
695
+ }
696
+ }
697
+ if (layoutBoundsUpdated) {
698
+ queueLayoutRefresh();
699
+ }
700
+ if (savedCount > 0) {
701
+ const statusMessage = savedCount === 1
702
+ ? `Setting saved (${lastScope})`
703
+ : `${savedCount} settings saved`;
704
+ setStatusMessage(statusMessage);
705
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
706
+ }
707
+ }
708
+ catch (error) {
709
+ setStatusMessage(`Failed to save setting: ${error?.message || String(error)}`);
710
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
711
+ }
315
712
  }
316
713
  }
317
714
  else if (input === "l") {
318
715
  // Open logs popup
319
- await popupManager.launchLogsPopup();
716
+ await popupManager.launchLogsPopup(getActiveProjectRoot());
320
717
  }
321
718
  else if (input === "h") {
322
- // Launch hooks authoring session directly
323
- await launchHooksAuthoringSession();
719
+ if (selectedIndex < panes.length) {
720
+ await togglePaneVisibility(panes[selectedIndex]);
721
+ }
722
+ else {
723
+ setStatusMessage("Select a pane to toggle visibility");
724
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
725
+ }
726
+ }
727
+ else if (input === "H") {
728
+ if (selectedIndex < panes.length) {
729
+ await toggleOtherPanesVisibility(panes[selectedIndex]);
730
+ }
731
+ else {
732
+ setStatusMessage("Select a pane to toggle the others");
733
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
734
+ }
735
+ }
736
+ else if (input === "P") {
737
+ await toggleProjectPanesVisibility();
324
738
  }
325
739
  else if (input === "?") {
326
740
  // Open keyboard shortcuts popup
327
- const shortcutsAction = await popupManager.launchShortcutsPopup(!!controlPaneId);
741
+ const shortcutsAction = await popupManager.launchShortcutsPopup(!!controlPaneId, getActiveProjectRoot());
328
742
  if (shortcutsAction === "hooks") {
329
743
  await launchHooksAuthoringSession();
330
744
  }
331
745
  }
332
746
  else if (input === "L" && controlPaneId) {
333
747
  // Reset layout to sidebar configuration (Shift+L)
334
- enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH);
335
- setStatusMessage("Layout reset");
336
- setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
748
+ try {
749
+ await enforceControlPaneSize(controlPaneId, SIDEBAR_WIDTH, { forceLayout: true });
750
+ setStatusMessage("Layout reset");
751
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
752
+ }
753
+ catch (error) {
754
+ setStatusMessage(`Failed to reset layout: ${error?.message || String(error)}`);
755
+ setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_LONG);
756
+ }
337
757
  }
338
758
  else if (input === "T") {
339
759
  // Demo toasts (Shift+T) - cycles through different types
@@ -369,7 +789,7 @@ export function useInputHandling(params) {
369
789
  setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
370
790
  return;
371
791
  }
372
- const result = await popupManager.launchReopenWorktreePopup(orphanedWorktrees);
792
+ const result = await popupManager.launchReopenWorktreePopup(orphanedWorktrees, targetProjectRoot);
373
793
  if (result) {
374
794
  await handleReopenWorktree(result.slug, result.path, targetProjectRoot);
375
795
  }