dmux 5.6.3 → 5.7.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 (280) hide show
  1. package/README.ja.md +93 -0
  2. package/README.md +16 -1
  3. package/dist/DmuxApp.d.ts.map +1 -1
  4. package/dist/DmuxApp.js +282 -43
  5. package/dist/DmuxApp.js.map +1 -1
  6. package/dist/actions/implementations/closeAction.d.ts.map +1 -1
  7. package/dist/actions/implementations/closeAction.js +85 -70
  8. package/dist/actions/implementations/closeAction.js.map +1 -1
  9. package/dist/actions/implementations/createPullRequestAction.d.ts +4 -0
  10. package/dist/actions/implementations/createPullRequestAction.d.ts.map +1 -0
  11. package/dist/actions/implementations/createPullRequestAction.js +218 -0
  12. package/dist/actions/implementations/createPullRequestAction.js.map +1 -0
  13. package/dist/actions/implementations/index.d.ts +1 -0
  14. package/dist/actions/implementations/index.d.ts.map +1 -1
  15. package/dist/actions/implementations/index.js +1 -0
  16. package/dist/actions/implementations/index.js.map +1 -1
  17. package/dist/actions/implementations/mergeAction.js +8 -3
  18. package/dist/actions/implementations/mergeAction.js.map +1 -1
  19. package/dist/actions/index.d.ts.map +1 -1
  20. package/dist/actions/index.js +2 -0
  21. package/dist/actions/index.js.map +1 -1
  22. package/dist/actions/paneActions.d.ts +1 -1
  23. package/dist/actions/paneActions.d.ts.map +1 -1
  24. package/dist/actions/paneActions.js +1 -1
  25. package/dist/actions/paneActions.js.map +1 -1
  26. package/dist/actions/types.d.ts +11 -2
  27. package/dist/actions/types.d.ts.map +1 -1
  28. package/dist/actions/types.js +9 -0
  29. package/dist/actions/types.js.map +1 -1
  30. package/dist/components/inputs/CleanTextInput.d.ts +1 -0
  31. package/dist/components/inputs/CleanTextInput.d.ts.map +1 -1
  32. package/dist/components/inputs/CleanTextInput.js +40 -42
  33. package/dist/components/inputs/CleanTextInput.js.map +1 -1
  34. package/dist/components/inputs/InlineCursorInput.d.ts +10 -0
  35. package/dist/components/inputs/InlineCursorInput.d.ts.map +1 -0
  36. package/dist/components/inputs/InlineCursorInput.js +101 -0
  37. package/dist/components/inputs/InlineCursorInput.js.map +1 -0
  38. package/dist/components/panes/PaneCard.d.ts +3 -1
  39. package/dist/components/panes/PaneCard.d.ts.map +1 -1
  40. package/dist/components/panes/PaneCard.js +14 -4
  41. package/dist/components/panes/PaneCard.js.map +1 -1
  42. package/dist/components/panes/PanesGrid.d.ts +4 -1
  43. package/dist/components/panes/PanesGrid.d.ts.map +1 -1
  44. package/dist/components/panes/PanesGrid.js +27 -15
  45. package/dist/components/panes/PanesGrid.js.map +1 -1
  46. package/dist/components/popups/config.d.ts +4 -4
  47. package/dist/components/popups/config.js +6 -6
  48. package/dist/components/popups/config.js.map +1 -1
  49. package/dist/components/popups/inputPopup.js +3 -3
  50. package/dist/components/popups/inputPopup.js.map +1 -1
  51. package/dist/components/popups/newPaneFieldNavigation.d.ts +9 -0
  52. package/dist/components/popups/newPaneFieldNavigation.d.ts.map +1 -0
  53. package/dist/components/popups/newPaneFieldNavigation.js +20 -0
  54. package/dist/components/popups/newPaneFieldNavigation.js.map +1 -0
  55. package/dist/components/popups/newPaneGitOptions.d.ts +23 -0
  56. package/dist/components/popups/newPaneGitOptions.d.ts.map +1 -0
  57. package/dist/components/popups/newPaneGitOptions.js +91 -0
  58. package/dist/components/popups/newPaneGitOptions.js.map +1 -0
  59. package/dist/components/popups/newPanePopup.d.ts +7 -2
  60. package/dist/components/popups/newPanePopup.d.ts.map +1 -1
  61. package/dist/components/popups/newPanePopup.js +256 -19
  62. package/dist/components/popups/newPanePopup.js.map +1 -1
  63. package/dist/components/popups/prReviewPopup.d.ts +12 -0
  64. package/dist/components/popups/prReviewPopup.d.ts.map +1 -0
  65. package/dist/components/popups/prReviewPopup.js +278 -0
  66. package/dist/components/popups/prReviewPopup.js.map +1 -0
  67. package/dist/components/popups/reopenWorktreePopup.js +1 -1
  68. package/dist/components/popups/reopenWorktreePopup.js.map +1 -1
  69. package/dist/components/popups/settingsPopup.js +72 -6
  70. package/dist/components/popups/settingsPopup.js.map +1 -1
  71. package/dist/components/ui/FooterHelp.d.ts.map +1 -1
  72. package/dist/components/ui/FooterHelp.js +5 -4
  73. package/dist/components/ui/FooterHelp.js.map +1 -1
  74. package/dist/hooks/useActionSystem.d.ts +12 -2
  75. package/dist/hooks/useActionSystem.d.ts.map +1 -1
  76. package/dist/hooks/useActionSystem.js +19 -1
  77. package/dist/hooks/useActionSystem.js.map +1 -1
  78. package/dist/hooks/useInputHandling.d.ts +5 -3
  79. package/dist/hooks/useInputHandling.d.ts.map +1 -1
  80. package/dist/hooks/useInputHandling.js +93 -32
  81. package/dist/hooks/useInputHandling.js.map +1 -1
  82. package/dist/hooks/usePaneCreation.d.ts +5 -3
  83. package/dist/hooks/usePaneCreation.d.ts.map +1 -1
  84. package/dist/hooks/usePaneCreation.js +16 -3
  85. package/dist/hooks/usePaneCreation.js.map +1 -1
  86. package/dist/hooks/usePaneLoading.d.ts.map +1 -1
  87. package/dist/hooks/usePaneLoading.js +28 -3
  88. package/dist/hooks/usePaneLoading.js.map +1 -1
  89. package/dist/hooks/usePaneSync.d.ts.map +1 -1
  90. package/dist/hooks/usePaneSync.js +8 -5
  91. package/dist/hooks/usePaneSync.js.map +1 -1
  92. package/dist/hooks/usePanes.d.ts.map +1 -1
  93. package/dist/hooks/usePanes.js +8 -1
  94. package/dist/hooks/usePanes.js.map +1 -1
  95. package/dist/hooks/useShellDetection.d.ts.map +1 -1
  96. package/dist/hooks/useShellDetection.js +7 -1
  97. package/dist/hooks/useShellDetection.js.map +1 -1
  98. package/dist/index.js +49 -22
  99. package/dist/index.js.map +1 -1
  100. package/dist/panes/decorative-pane.js +57 -27
  101. package/dist/panes/decorative-pane.js.map +1 -1
  102. package/dist/server/embedded-assets.d.ts.map +1 -1
  103. package/dist/server/embedded-assets.js +4 -12
  104. package/dist/server/embedded-assets.js.map +1 -1
  105. package/dist/services/DmuxFocusService.d.ts.map +1 -1
  106. package/dist/services/DmuxFocusService.js +19 -14
  107. package/dist/services/DmuxFocusService.js.map +1 -1
  108. package/dist/services/PaneEventService.d.ts +11 -0
  109. package/dist/services/PaneEventService.d.ts.map +1 -1
  110. package/dist/services/PaneEventService.js +31 -3
  111. package/dist/services/PaneEventService.js.map +1 -1
  112. package/dist/services/PaneWorkerManager.d.ts.map +1 -1
  113. package/dist/services/PaneWorkerManager.js +1 -0
  114. package/dist/services/PaneWorkerManager.js.map +1 -1
  115. package/dist/services/PopupManager.d.ts +23 -6
  116. package/dist/services/PopupManager.d.ts.map +1 -1
  117. package/dist/services/PopupManager.js +170 -33
  118. package/dist/services/PopupManager.js.map +1 -1
  119. package/dist/services/StatusDetector.d.ts +1 -0
  120. package/dist/services/StatusDetector.d.ts.map +1 -1
  121. package/dist/services/StatusDetector.js +44 -0
  122. package/dist/services/StatusDetector.js.map +1 -1
  123. package/dist/services/TmuxHookManager.d.ts.map +1 -1
  124. package/dist/services/TmuxHookManager.js +3 -2
  125. package/dist/services/TmuxHookManager.js.map +1 -1
  126. package/dist/services/TmuxService.d.ts +7 -0
  127. package/dist/services/TmuxService.d.ts.map +1 -1
  128. package/dist/services/TmuxService.js +15 -1
  129. package/dist/services/TmuxService.js.map +1 -1
  130. package/dist/theme/colors.d.ts +24 -10
  131. package/dist/theme/colors.d.ts.map +1 -1
  132. package/dist/theme/colors.js +143 -19
  133. package/dist/theme/colors.js.map +1 -1
  134. package/dist/theme/themePalette.d.ts +6 -0
  135. package/dist/theme/themePalette.d.ts.map +1 -0
  136. package/dist/theme/themePalette.js +18 -0
  137. package/dist/theme/themePalette.js.map +1 -0
  138. package/dist/types.d.ts +13 -0
  139. package/dist/types.d.ts.map +1 -1
  140. package/dist/utils/agentLaunch.d.ts +2 -0
  141. package/dist/utils/agentLaunch.d.ts.map +1 -1
  142. package/dist/utils/agentLaunch.js +10 -9
  143. package/dist/utils/agentLaunch.js.map +1 -1
  144. package/dist/utils/aiMerge.d.ts +4 -0
  145. package/dist/utils/aiMerge.d.ts.map +1 -1
  146. package/dist/utils/aiMerge.js +7 -4
  147. package/dist/utils/aiMerge.js.map +1 -1
  148. package/dist/utils/asciiArt.d.ts.map +1 -1
  149. package/dist/utils/asciiArt.js +2 -1
  150. package/dist/utils/asciiArt.js.map +1 -1
  151. package/dist/utils/attachAgent.d.ts.map +1 -1
  152. package/dist/utils/attachAgent.js +20 -1
  153. package/dist/utils/attachAgent.js.map +1 -1
  154. package/dist/utils/codexHooks.d.ts +25 -0
  155. package/dist/utils/codexHooks.d.ts.map +1 -0
  156. package/dist/utils/codexHooks.js +131 -0
  157. package/dist/utils/codexHooks.js.map +1 -0
  158. package/dist/utils/conflictResolutionPane.d.ts.map +1 -1
  159. package/dist/utils/conflictResolutionPane.js +4 -0
  160. package/dist/utils/conflictResolutionPane.js.map +1 -1
  161. package/dist/utils/devWatchCommand.d.ts.map +1 -1
  162. package/dist/utils/devWatchCommand.js +6 -2
  163. package/dist/utils/devWatchCommand.js.map +1 -1
  164. package/dist/utils/dmuxCommand.d.ts +4 -3
  165. package/dist/utils/dmuxCommand.d.ts.map +1 -1
  166. package/dist/utils/dmuxCommand.js +15 -14
  167. package/dist/utils/dmuxCommand.js.map +1 -1
  168. package/dist/utils/generated-agents-doc.d.ts +1 -1
  169. package/dist/utils/generated-agents-doc.d.ts.map +1 -1
  170. package/dist/utils/generated-agents-doc.js +46 -11
  171. package/dist/utils/generated-agents-doc.js.map +1 -1
  172. package/dist/utils/git.d.ts +8 -0
  173. package/dist/utils/git.d.ts.map +1 -1
  174. package/dist/utils/git.js +26 -0
  175. package/dist/utils/git.js.map +1 -1
  176. package/dist/utils/githubPullRequest.d.ts +18 -0
  177. package/dist/utils/githubPullRequest.d.ts.map +1 -0
  178. package/dist/utils/githubPullRequest.js +196 -0
  179. package/dist/utils/githubPullRequest.js.map +1 -0
  180. package/dist/utils/hooks.d.ts +15 -0
  181. package/dist/utils/hooks.d.ts.map +1 -1
  182. package/dist/utils/hooks.js +105 -0
  183. package/dist/utils/hooks.js.map +1 -1
  184. package/dist/utils/hooksDocs.d.ts +1 -1
  185. package/dist/utils/hooksDocs.d.ts.map +1 -1
  186. package/dist/utils/hooksDocs.js +21 -11
  187. package/dist/utils/hooksDocs.js.map +1 -1
  188. package/dist/utils/input.d.ts +7 -6
  189. package/dist/utils/input.d.ts.map +1 -1
  190. package/dist/utils/input.js +47 -24
  191. package/dist/utils/input.js.map +1 -1
  192. package/dist/utils/paneBootstrapConfig.d.ts +27 -0
  193. package/dist/utils/paneBootstrapConfig.d.ts.map +1 -0
  194. package/dist/utils/paneBootstrapConfig.js +2 -0
  195. package/dist/utils/paneBootstrapConfig.js.map +1 -0
  196. package/dist/utils/paneBootstrapRunner.d.ts +3 -0
  197. package/dist/utils/paneBootstrapRunner.d.ts.map +1 -0
  198. package/dist/utils/paneBootstrapRunner.js +517 -0
  199. package/dist/utils/paneBootstrapRunner.js.map +1 -0
  200. package/dist/utils/paneColors.d.ts +7 -0
  201. package/dist/utils/paneColors.d.ts.map +1 -0
  202. package/dist/utils/paneColors.js +42 -0
  203. package/dist/utils/paneColors.js.map +1 -0
  204. package/dist/utils/paneCreation.d.ts +3 -1
  205. package/dist/utils/paneCreation.d.ts.map +1 -1
  206. package/dist/utils/paneCreation.js +165 -225
  207. package/dist/utils/paneCreation.js.map +1 -1
  208. package/dist/utils/paneNaming.d.ts +16 -0
  209. package/dist/utils/paneNaming.d.ts.map +1 -0
  210. package/dist/utils/paneNaming.js +47 -0
  211. package/dist/utils/paneNaming.js.map +1 -0
  212. package/dist/utils/paneTitle.d.ts +1 -0
  213. package/dist/utils/paneTitle.d.ts.map +1 -1
  214. package/dist/utils/paneTitle.js +5 -1
  215. package/dist/utils/paneTitle.js.map +1 -1
  216. package/dist/utils/paneTitlePrefix.d.ts +8 -0
  217. package/dist/utils/paneTitlePrefix.d.ts.map +1 -0
  218. package/dist/utils/paneTitlePrefix.js +20 -0
  219. package/dist/utils/paneTitlePrefix.js.map +1 -0
  220. package/dist/utils/pathEnvironment.d.ts +6 -0
  221. package/dist/utils/pathEnvironment.d.ts.map +1 -0
  222. package/dist/utils/pathEnvironment.js +59 -0
  223. package/dist/utils/pathEnvironment.js.map +1 -0
  224. package/dist/utils/popup.d.ts +1 -0
  225. package/dist/utils/popup.d.ts.map +1 -1
  226. package/dist/utils/popup.js +9 -2
  227. package/dist/utils/popup.js.map +1 -1
  228. package/dist/utils/prSummary.d.ts +34 -0
  229. package/dist/utils/prSummary.d.ts.map +1 -0
  230. package/dist/utils/prSummary.js +130 -0
  231. package/dist/utils/prSummary.js.map +1 -0
  232. package/dist/utils/projectActions.d.ts +6 -0
  233. package/dist/utils/projectActions.d.ts.map +1 -1
  234. package/dist/utils/projectActions.js +46 -0
  235. package/dist/utils/projectActions.js.map +1 -1
  236. package/dist/utils/reopenWorktree.d.ts.map +1 -1
  237. package/dist/utils/reopenWorktree.js +37 -5
  238. package/dist/utils/reopenWorktree.js.map +1 -1
  239. package/dist/utils/resumeBranches.d.ts.map +1 -1
  240. package/dist/utils/resumeBranches.js +9 -3
  241. package/dist/utils/resumeBranches.js.map +1 -1
  242. package/dist/utils/settingsManager.d.ts +11 -3
  243. package/dist/utils/settingsManager.d.ts.map +1 -1
  244. package/dist/utils/settingsManager.js +106 -21
  245. package/dist/utils/settingsManager.js.map +1 -1
  246. package/dist/utils/shellPaneDetection.d.ts.map +1 -1
  247. package/dist/utils/shellPaneDetection.js +4 -0
  248. package/dist/utils/shellPaneDetection.js.map +1 -1
  249. package/dist/utils/sidebarProjects.d.ts +10 -3
  250. package/dist/utils/sidebarProjects.d.ts.map +1 -1
  251. package/dist/utils/sidebarProjects.js +125 -10
  252. package/dist/utils/sidebarProjects.js.map +1 -1
  253. package/dist/utils/tmuxHookCommands.d.ts +6 -0
  254. package/dist/utils/tmuxHookCommands.d.ts.map +1 -1
  255. package/dist/utils/tmuxHookCommands.js +12 -0
  256. package/dist/utils/tmuxHookCommands.js.map +1 -1
  257. package/dist/utils/tmuxSendKeys.d.ts +2 -0
  258. package/dist/utils/tmuxSendKeys.d.ts.map +1 -0
  259. package/dist/utils/tmuxSendKeys.js +8 -0
  260. package/dist/utils/tmuxSendKeys.js.map +1 -0
  261. package/dist/utils/toastLayout.d.ts +10 -0
  262. package/dist/utils/toastLayout.d.ts.map +1 -0
  263. package/dist/utils/toastLayout.js +24 -0
  264. package/dist/utils/toastLayout.js.map +1 -0
  265. package/dist/utils/welcomePane.d.ts +5 -1
  266. package/dist/utils/welcomePane.d.ts.map +1 -1
  267. package/dist/utils/welcomePane.js +33 -3
  268. package/dist/utils/welcomePane.js.map +1 -1
  269. package/dist/utils/welcomePaneManager.d.ts +3 -1
  270. package/dist/utils/welcomePaneManager.d.ts.map +1 -1
  271. package/dist/utils/welcomePaneManager.js +40 -2
  272. package/dist/utils/welcomePaneManager.js.map +1 -1
  273. package/dist/workers/PaneWorker.js +77 -0
  274. package/dist/workers/PaneWorker.js.map +1 -1
  275. package/dist/workers/WorkerMessages.d.ts +9 -1
  276. package/dist/workers/WorkerMessages.d.ts.map +1 -1
  277. package/dist/workers/WorkerMessages.js.map +1 -1
  278. package/dist/workers/panePollingWorker.js +33 -2
  279. package/dist/workers/panePollingWorker.js.map +1 -1
  280. package/package.json +1 -1
package/dist/DmuxApp.js CHANGED
@@ -1,5 +1,6 @@
1
- import React, { useState, useEffect, useMemo } from "react";
1
+ import React, { useState, useEffect, useMemo, useRef } from "react";
2
2
  import { Box, Text, useApp, useStdout, useInput } from "ink";
3
+ import stringWidth from "string-width";
3
4
  import { TmuxService } from "./services/TmuxService.js";
4
5
  // Hooks
5
6
  import usePanes from "./hooks/usePanes.js";
@@ -43,14 +44,21 @@ import { getPaneDisplayName } from "./utils/paneTitle.js";
43
44
  import { FOOTER_TIP_ROTATION_INTERVAL, getFooterTips, getNextFooterTipIndex, getRandomFooterTipIndex, } from "./utils/footerTips.js";
44
45
  const __filename = fileURLToPath(import.meta.url);
45
46
  const __dirname = dirname(__filename);
47
+ const ACTIVE_PANE_SYNC_INTERVAL_MS = 125;
46
48
  import PanesGrid from "./components/panes/PanesGrid.js";
47
49
  import CommandPromptDialog from "./components/dialogs/CommandPromptDialog.js";
48
50
  import FileCopyPrompt from "./components/ui/FileCopyPrompt.js";
49
51
  import FooterHelp from "./components/ui/FooterHelp.js";
50
52
  import TmuxHooksPromptDialog from "./components/dialogs/TmuxHooksPromptDialog.js";
51
53
  import { PaneEventService } from "./services/PaneEventService.js";
52
- import { buildProjectActionLayout, buildVisualNavigationRows, buildGroupStartRows, getProjectActionByIndex, } from "./utils/projectActions.js";
54
+ import { buildProjectActionLayout, buildVisualNavigationRows, buildGroupStartRows, getProjectActionByIndex, resolveSelectionAfterPaneClose, } from "./utils/projectActions.js";
53
55
  import { getPaneProjectRoot } from "./utils/paneProject.js";
56
+ import { applyDmuxTheme, getDmuxThemePalette, } from "./theme/colors.js";
57
+ import { applyTmuxThemeToSession, refreshWelcomePaneTheme, } from "./utils/welcomePane.js";
58
+ import { syncWelcomePaneVisibility } from "./utils/welcomePaneManager.js";
59
+ import { getPaneColorTheme, resolveProjectColorTheme, } from "./utils/paneColors.js";
60
+ import { getPaneTitlePrefixValue, paneNeedsAnimatedTitlePrefix, PANE_TITLE_BUSY_FRAMES, } from "./utils/paneTitlePrefix.js";
61
+ import { getPaneTmuxDisplayTitle } from "./utils/paneTitle.js";
54
62
  const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoot, autoUpdater, controlPaneId, }) => {
55
63
  const { stdout } = useStdout();
56
64
  const terminalHeight = stdout?.rows || 40;
@@ -58,13 +66,19 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
58
66
  const sessionProjectRoot = projectRoot || process.cwd();
59
67
  /* panes state moved to usePanes */
60
68
  const [selectedIndex, setSelectedIndex] = useState(0);
69
+ const [focusedPaneId, setFocusedPaneId] = useState(null);
61
70
  const { statusMessage, setStatusMessage } = useStatusMessages();
62
71
  const [isCreatingPane, setIsCreatingPane] = useState(false);
63
72
  const { trackProjectActivity, isProjectBusy: isTrackedProjectBusy, } = useProjectActivity(sessionProjectRoot);
64
73
  // Settings state
65
74
  const [settingsManager] = useState(() => new SettingsManager(projectRoot));
66
75
  const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
67
- const settings = settingsManager.getSettings();
76
+ const [themeRefreshNonce, setThemeRefreshNonce] = useState(0);
77
+ const [settings, setSettings] = useState(() => new SettingsManager(sessionProjectRoot).getSettings());
78
+ const paneTitlePrefixCacheRef = useRef(new Map());
79
+ const paneTitleLabelCacheRef = useRef(new Map());
80
+ const paneActiveBorderStyleCacheRef = useRef(new Map());
81
+ const paneTitleSpinnerFrameRef = useRef(0);
68
82
  // Dialog state management
69
83
  const dialogState = useDialogState();
70
84
  const { showCommandPrompt, setShowCommandPrompt, commandInput, setCommandInput, showFileCopyPrompt, setShowFileCopyPrompt, currentCommandType, setCurrentCommandType, runningCommand, setRunningCommand, quitConfirmMode, setQuitConfirmMode, } = dialogState;
@@ -78,6 +92,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
78
92
  // Agent selection is settings-driven.
79
93
  // Installation checks are performed lazily in the Enabled Agents settings popup.
80
94
  const availableAgents = resolveEnabledAgentsSelection(settings.enabledAgents);
95
+ const getAvailableAgentsForProject = (targetProjectRoot = selectedProjectRoot) => resolveEnabledAgentsSelection(new SettingsManager(targetProjectRoot).getSettings().enabledAgents);
81
96
  const footerTips = useMemo(() => getFooterTips(isDevMode), [isDevMode]);
82
97
  const showFooterTips = settings.showFooterTips !== false && footerTips.length > 0;
83
98
  const [footerTipIndex, setFooterTipIndex] = useState(() => getRandomFooterTipIndex(footerTips.length));
@@ -133,12 +148,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
133
148
  };
134
149
  }, []);
135
150
  // Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
136
- const { panes, setPanes, sidebarProjects, isLoading, loadPanes, savePanes, saveSidebarProjects, } = usePanes(panesFile, false, sessionName, controlPaneId, useHooks);
151
+ const { panes, setPanes, sidebarProjects, isLoading, loadPanes, savePanes, saveSidebarProjects, eventMode, } = usePanes(panesFile, false, sessionName, controlPaneId, useHooks);
137
152
  // Check for tmux hooks preference on startup
138
153
  useEffect(() => {
139
154
  const checkHooksPreference = async () => {
140
155
  // Check if user already has a preference
141
- const settings = settingsManager.getSettings();
156
+ const settings = new SettingsManager(sessionProjectRoot).getSettings();
142
157
  if (settings.useTmuxHooks !== undefined) {
143
158
  // User has already decided
144
159
  setUseHooks(settings.useTmuxHooks);
@@ -153,6 +168,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
153
168
  setUseHooks(true);
154
169
  // Save the preference
155
170
  settingsManager.updateSetting('useTmuxHooks', true, 'global');
171
+ refreshDmuxSettings();
156
172
  }
157
173
  else {
158
174
  // Need to ask user - show prompt
@@ -347,18 +363,163 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
347
363
  // getPanePositions moved to utils/tmux
348
364
  const activeDevSourcePath = isDevMode ? process.cwd() : undefined;
349
365
  const projectActionLayout = useMemo(() => buildProjectActionLayout(panes, sidebarProjects, sessionProjectRoot, projectName), [panes, sidebarProjects, sessionProjectRoot, projectName]);
366
+ const selectedPane = useMemo(() => {
367
+ for (const group of projectActionLayout.groups) {
368
+ const entry = group.panes.find((candidate) => candidate.index === selectedIndex);
369
+ if (entry) {
370
+ return entry.pane;
371
+ }
372
+ }
373
+ return undefined;
374
+ }, [projectActionLayout.groups, selectedIndex]);
350
375
  const selectedProjectRoot = useMemo(() => {
351
- const selectedPane = selectedIndex < panes.length ? panes[selectedIndex] : undefined;
352
376
  if (selectedPane) {
353
377
  return getPaneProjectRoot(selectedPane, sessionProjectRoot);
354
378
  }
355
379
  return (getProjectActionByIndex(projectActionLayout.actionItems, selectedIndex)?.projectRoot
356
380
  || sessionProjectRoot);
357
- }, [selectedIndex, panes, projectActionLayout.actionItems, sessionProjectRoot]);
381
+ }, [selectedPane, selectedIndex, projectActionLayout.actionItems, sessionProjectRoot]);
382
+ const focusedPane = useMemo(() => focusedPaneId
383
+ ? panes.find((pane) => pane.paneId === focusedPaneId)
384
+ : undefined, [focusedPaneId, panes]);
385
+ const activeProjectRoot = selectedProjectRoot;
386
+ const resolveProjectThemeName = React.useCallback((activeProjectRoot) => {
387
+ return resolveProjectColorTheme(activeProjectRoot, sidebarProjects);
388
+ }, [sidebarProjects]);
389
+ const activeBorderPane = focusedPane || selectedPane;
390
+ const activeBorderPaneId = activeBorderPane?.paneId;
391
+ const selectedThemeName = useMemo(() => resolveProjectThemeName(activeProjectRoot), [
392
+ resolveProjectThemeName,
393
+ activeProjectRoot,
394
+ themeRefreshNonce,
395
+ ]);
396
+ const visiblePaneCount = useMemo(() => panes.filter((pane) => !pane.hidden).length, [panes]);
397
+ const controlPaneActiveBorderStyle = useMemo(() => `fg=colour${getDmuxThemePalette(selectedThemeName).activeBorder}`, [selectedThemeName]);
398
+ const projectThemeByRoot = useMemo(() => {
399
+ const themeMap = new Map();
400
+ for (const group of projectActionLayout.groups) {
401
+ const paneTheme = group.panes.find((entry) => entry.pane.colorTheme)?.pane.colorTheme;
402
+ themeMap.set(group.projectRoot, paneTheme || resolveProjectThemeName(group.projectRoot));
403
+ }
404
+ return themeMap;
405
+ }, [projectActionLayout.groups, resolveProjectThemeName, themeRefreshNonce]);
406
+ applyDmuxTheme(selectedThemeName);
407
+ const refreshDmuxSettings = (_activeProjectRoot = selectedProjectRoot) => {
408
+ setSettings(new SettingsManager(sessionProjectRoot).getSettings());
409
+ setThemeRefreshNonce((current) => current + 1);
410
+ };
358
411
  const navigationRows = useMemo(() => isLoading
359
412
  ? projectActionLayout.groups.flatMap((group) => group.panes.map((entry) => [entry.index]))
360
413
  : buildVisualNavigationRows(projectActionLayout), [isLoading, projectActionLayout]);
361
414
  const groupStartRows = useMemo(() => isLoading ? [] : buildGroupStartRows(projectActionLayout), [isLoading, projectActionLayout]);
415
+ useEffect(() => {
416
+ try {
417
+ applyTmuxThemeToSession(sessionName, activeProjectRoot, selectedThemeName);
418
+ }
419
+ catch {
420
+ // Theme updates are best-effort at runtime.
421
+ }
422
+ void refreshWelcomePaneTheme(panesFile, activeProjectRoot, selectedThemeName);
423
+ }, [panesFile, activeProjectRoot, selectedThemeName, sessionName]);
424
+ useEffect(() => {
425
+ if (isLoading) {
426
+ return;
427
+ }
428
+ void syncWelcomePaneVisibility(sessionProjectRoot, controlPaneId, visiblePaneCount === 0, selectedThemeName);
429
+ }, [
430
+ controlPaneId,
431
+ isLoading,
432
+ selectedThemeName,
433
+ sessionProjectRoot,
434
+ visiblePaneCount,
435
+ ]);
436
+ useEffect(() => {
437
+ if (!process.env.TMUX) {
438
+ return;
439
+ }
440
+ const tmuxService = TmuxService.getInstance();
441
+ const syncPaneTitlePrefixes = () => {
442
+ const cachedPrefixes = paneTitlePrefixCacheRef.current;
443
+ const cachedLabels = paneTitleLabelCacheRef.current;
444
+ const cachedActiveBorderStyles = paneActiveBorderStyleCacheRef.current;
445
+ const activePaneIds = new Set(panes.map((pane) => pane.paneId));
446
+ const activeBorderStylePaneIds = new Set(activePaneIds);
447
+ if (controlPaneId) {
448
+ activeBorderStylePaneIds.add(controlPaneId);
449
+ }
450
+ for (const paneId of Array.from(cachedPrefixes.keys())) {
451
+ if (!activePaneIds.has(paneId)) {
452
+ tmuxService.unsetPaneOptionSync(paneId, '@dmux_title_prefix');
453
+ cachedPrefixes.delete(paneId);
454
+ }
455
+ }
456
+ for (const paneId of Array.from(cachedLabels.keys())) {
457
+ if (!activePaneIds.has(paneId)) {
458
+ tmuxService.unsetPaneOptionSync(paneId, '@dmux_title_label');
459
+ cachedLabels.delete(paneId);
460
+ }
461
+ }
462
+ for (const paneId of Array.from(cachedActiveBorderStyles.keys())) {
463
+ if (!activeBorderStylePaneIds.has(paneId)) {
464
+ tmuxService.unsetPaneOptionSync(paneId, '@dmux_active_border_style');
465
+ cachedActiveBorderStyles.delete(paneId);
466
+ }
467
+ }
468
+ for (const pane of panes) {
469
+ const paneThemeName = getPaneColorTheme(pane, sidebarProjects, sessionProjectRoot);
470
+ const prefixValue = getPaneTitlePrefixValue(pane, sidebarProjects, sessionProjectRoot, paneTitleSpinnerFrameRef.current);
471
+ const labelValue = getPaneTmuxDisplayTitle(pane, sessionProjectRoot, projectName);
472
+ const activeBorderStyle = `fg=colour${getDmuxThemePalette(paneThemeName).activeBorder}`;
473
+ if (cachedPrefixes.get(pane.paneId) !== prefixValue) {
474
+ tmuxService.setPaneOptionSync(pane.paneId, '@dmux_title_prefix', prefixValue);
475
+ cachedPrefixes.set(pane.paneId, prefixValue);
476
+ }
477
+ if (cachedLabels.get(pane.paneId) !== labelValue) {
478
+ tmuxService.setPaneOptionSync(pane.paneId, '@dmux_title_label', labelValue);
479
+ cachedLabels.set(pane.paneId, labelValue);
480
+ }
481
+ if (cachedActiveBorderStyles.get(pane.paneId) !== activeBorderStyle) {
482
+ tmuxService.setPaneOptionSync(pane.paneId, '@dmux_active_border_style', activeBorderStyle);
483
+ cachedActiveBorderStyles.set(pane.paneId, activeBorderStyle);
484
+ }
485
+ if (pane.paneId === activeBorderPaneId) {
486
+ tmuxService.setSessionOptionSync(sessionName, 'pane-active-border-style', activeBorderStyle);
487
+ }
488
+ }
489
+ if (controlPaneId && cachedActiveBorderStyles.get(controlPaneId) !== controlPaneActiveBorderStyle) {
490
+ tmuxService.setPaneOptionSync(controlPaneId, '@dmux_active_border_style', controlPaneActiveBorderStyle);
491
+ cachedActiveBorderStyles.set(controlPaneId, controlPaneActiveBorderStyle);
492
+ }
493
+ if (!focusedPane) {
494
+ tmuxService.setSessionOptionSync(sessionName, 'pane-active-border-style', controlPaneActiveBorderStyle);
495
+ }
496
+ };
497
+ const hasAnimatedPrefix = panes.some(paneNeedsAnimatedTitlePrefix);
498
+ if (!hasAnimatedPrefix) {
499
+ paneTitleSpinnerFrameRef.current = 0;
500
+ }
501
+ syncPaneTitlePrefixes();
502
+ if (!hasAnimatedPrefix) {
503
+ return;
504
+ }
505
+ const interval = setInterval(() => {
506
+ paneTitleSpinnerFrameRef.current = (paneTitleSpinnerFrameRef.current + 1) % PANE_TITLE_BUSY_FRAMES.length;
507
+ syncPaneTitlePrefixes();
508
+ }, 90);
509
+ return () => {
510
+ clearInterval(interval);
511
+ };
512
+ }, [
513
+ panes,
514
+ sidebarProjects,
515
+ sessionProjectRoot,
516
+ projectName,
517
+ activeBorderPaneId,
518
+ sessionName,
519
+ controlPaneId,
520
+ controlPaneActiveBorderStyle,
521
+ focusedPane,
522
+ ]);
362
523
  useEffect(() => {
363
524
  const maxIndex = Math.max(0, projectActionLayout.totalItems - 1);
364
525
  if (selectedIndex > maxIndex) {
@@ -368,14 +529,59 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
368
529
  // Navigation logic moved to hook
369
530
  const { getCardGridPosition, findCardInDirection } = useNavigation(navigationRows, groupStartRows);
370
531
  // findCardInDirection provided by useNavigation
532
+ const syncSelectedIndexToFocusedPane = React.useCallback(async (activePaneId) => {
533
+ try {
534
+ const focusedPaneId = activePaneId ?? await TmuxService.getInstance().getActivePaneId();
535
+ if (!focusedPaneId || focusedPaneId === controlPaneId) {
536
+ setFocusedPaneId(null);
537
+ return;
538
+ }
539
+ setFocusedPaneId((currentPaneId) => currentPaneId === focusedPaneId ? currentPaneId : focusedPaneId);
540
+ const focusedIndex = panes.findIndex((pane) => pane.paneId === focusedPaneId);
541
+ if (focusedIndex === -1) {
542
+ return;
543
+ }
544
+ setSelectedIndex((currentIndex) => currentIndex === focusedIndex ? currentIndex : focusedIndex);
545
+ }
546
+ catch {
547
+ // Focus sync is best-effort; pane lifecycle handling will correct stale IDs.
548
+ }
549
+ }, [controlPaneId, panes]);
550
+ useEffect(() => {
551
+ const paneEventService = PaneEventService.getInstance();
552
+ return paneEventService.onPaneFocusChanged((event) => {
553
+ void syncSelectedIndexToFocusedPane(event.activePaneId);
554
+ });
555
+ }, [syncSelectedIndexToFocusedPane]);
556
+ useEffect(() => {
557
+ if (!process.env.TMUX || panes.length === 0) {
558
+ return;
559
+ }
560
+ let syncInFlight = false;
561
+ const syncActivePane = () => {
562
+ if (syncInFlight) {
563
+ return;
564
+ }
565
+ syncInFlight = true;
566
+ void syncSelectedIndexToFocusedPane().finally(() => {
567
+ syncInFlight = false;
568
+ });
569
+ };
570
+ syncActivePane();
571
+ const interval = setInterval(syncActivePane, ACTIVE_PANE_SYNC_INTERVAL_MS);
572
+ return () => {
573
+ clearInterval(interval);
574
+ };
575
+ }, [eventMode, panes.length, syncSelectedIndexToFocusedPane]);
371
576
  // savePanes moved to usePanes
372
577
  // applySmartLayout moved to utils/tmux
373
578
  // Helper function to handle agent choice and pane creation
374
579
  const selectAgentsForPaneCreation = async (targetProjectRoot) => {
375
- if (availableAgents.length === 0) {
580
+ const targetRoot = targetProjectRoot || selectedProjectRoot;
581
+ if (getAvailableAgentsForProject(targetRoot).length === 0) {
376
582
  return [];
377
583
  }
378
- const selectedAgents = await popupManager.launchAgentChoicePopup(targetProjectRoot || selectedProjectRoot);
584
+ const selectedAgents = await popupManager.launchAgentChoicePopup(targetRoot);
379
585
  if (selectedAgents === null) {
380
586
  return null;
381
587
  }
@@ -386,9 +592,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
386
592
  }
387
593
  return selectedAgents;
388
594
  };
389
- const createPaneSelection = async (prompt, selectedAgents, targetProjectRoot, createOptions) => {
595
+ const createPaneSelection = async (paneInput, selectedAgents, targetProjectRoot, createOptions) => {
390
596
  if (selectedAgents.length === 0) {
391
- const pane = await createNewPaneHook(prompt, undefined, {
597
+ const pane = await createNewPaneHook(paneInput, undefined, {
392
598
  targetProjectRoot,
393
599
  skipAgentSelection: true,
394
600
  startPointBranch: createOptions?.startPointBranch,
@@ -396,7 +602,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
396
602
  });
397
603
  return pane ? 1 : 0;
398
604
  }
399
- const createdPanes = await createPanesForAgentsHook(prompt, selectedAgents, {
605
+ const createdPanes = await createPanesForAgentsHook(paneInput, selectedAgents, {
400
606
  existingPanes: panes,
401
607
  targetProjectRoot,
402
608
  startPointBranch: createOptions?.startPointBranch,
@@ -404,12 +610,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
404
610
  });
405
611
  return createdPanes.length;
406
612
  };
407
- const handlePaneCreationWithAgent = async (prompt, targetProjectRoot, createOptions) => {
613
+ const handlePaneCreationWithAgent = async (paneInput, targetProjectRoot, createOptions) => {
408
614
  const selectedAgents = await selectAgentsForPaneCreation(targetProjectRoot);
409
615
  if (selectedAgents === null) {
410
616
  return;
411
617
  }
412
- await createPaneSelection(prompt, selectedAgents, targetProjectRoot, createOptions);
618
+ await createPaneSelection(paneInput, selectedAgents, targetProjectRoot, createOptions);
413
619
  };
414
620
  const handleCreateChildWorktree = async (parentPane) => {
415
621
  if (!parentPane.worktreePath) {
@@ -418,8 +624,8 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
418
624
  return;
419
625
  }
420
626
  const targetProjectRoot = getPaneProjectRoot(parentPane, sessionProjectRoot);
421
- const promptValue = await popupManager.launchNewPanePopup(targetProjectRoot);
422
- if (!promptValue) {
627
+ const paneInput = await popupManager.launchNewPanePopup(targetProjectRoot);
628
+ if (!paneInput) {
423
629
  return;
424
630
  }
425
631
  const selectedAgents = await selectAgentsForPaneCreation(targetProjectRoot);
@@ -427,7 +633,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
427
633
  return;
428
634
  }
429
635
  const createSubWorktree = async () => {
430
- const createdCount = await createPaneSelection(promptValue, selectedAgents, targetProjectRoot, {
636
+ const createdCount = await createPaneSelection(paneInput, selectedAgents, targetProjectRoot, {
431
637
  startPointBranch: getPaneBranchName(parentPane),
432
638
  mergeTargetChain: createMergeTargetChain(parentPane, targetProjectRoot),
433
639
  });
@@ -514,7 +720,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
514
720
  const reopenProjectRoot = targetProjectRoot || projectRoot || process.cwd();
515
721
  let selectedAgent;
516
722
  if (!candidate.path) {
517
- if (availableAgents.length === 0) {
723
+ if (getAvailableAgentsForProject(reopenProjectRoot).length === 0) {
518
724
  setStatusMessage("No enabled agents available for opening this branch");
519
725
  setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
520
726
  return;
@@ -632,7 +838,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
632
838
  else if (result.type === "input") {
633
839
  if (!result.onSubmit)
634
840
  return;
635
- const inputValue = await popupManager.launchInputPopup(result.title || "Input", result.message, result.placeholder, result.defaultValue, selectedProjectRoot);
841
+ const inputValue = await popupManager.launchInputPopup(result.title || "Input", result.message, result.placeholder, result.defaultValue, selectedProjectRoot, result.inputMaxVisibleLines);
636
842
  if (inputValue !== null) {
637
843
  const nextResult = await trackProjectActivity(() => result.onSubmit(inputValue), selectedProjectRoot);
638
844
  // Recursively handle nested results
@@ -641,6 +847,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
641
847
  }
642
848
  }
643
849
  }
850
+ else if (result.type === "pr_review") {
851
+ if (!result.onSubmit || !result.reviewData)
852
+ return;
853
+ const inputValue = await popupManager.launchPRReviewPopup({
854
+ title: result.title || "Pull Request",
855
+ message: result.message || "",
856
+ defaultValue: result.defaultValue || "",
857
+ repoPath: result.reviewData.repoPath,
858
+ sourceBranch: result.reviewData.sourceBranch,
859
+ targetBranch: result.reviewData.targetBranch,
860
+ files: result.reviewData.files,
861
+ aiFailed: result.reviewData.aiFailed,
862
+ }, selectedProjectRoot);
863
+ if (inputValue !== null) {
864
+ const nextResult = await trackProjectActivity(() => result.onSubmit(inputValue), selectedProjectRoot);
865
+ if (nextResult) {
866
+ await handleActionResult(nextResult);
867
+ }
868
+ }
869
+ }
644
870
  else if (result.type === "navigation") {
645
871
  // Navigate to target pane if specified
646
872
  if (result.targetPaneId) {
@@ -673,25 +899,25 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
673
899
  projectName,
674
900
  defaultProjectRoot: sessionProjectRoot,
675
901
  onPaneRemove: async (paneId) => {
676
- // Mark pane as closing to prevent race condition with worker
677
- await lifecycleManager.beginClose(paneId, 'user requested');
678
- // Adjust selectedIndex before removing from list
679
- const removedIndex = panes.findIndex((p) => p.paneId === paneId);
680
- if (removedIndex >= 0 && selectedIndex >= panes.length - 1) {
681
- setSelectedIndex(Math.max(0, panes.length - 2));
682
- }
683
- // Remove from panes list
684
- const updatedPanes = panes.filter((p) => p.paneId !== paneId);
685
- savePanes(updatedPanes);
686
- // Mark close as completed (no more lock needed)
687
- await lifecycleManager.completeClose(paneId);
688
- // Return focus to control pane
689
- if (controlPaneId) {
902
+ const nextSelection = resolveSelectionAfterPaneClose(panes, paneId, sidebarProjects, sessionProjectRoot, projectName);
903
+ if (nextSelection) {
904
+ setSelectedIndex(nextSelection.selectedIndex);
905
+ }
906
+ else {
907
+ const maxIndex = Math.max(0, projectActionLayout.totalItems - 2);
908
+ if (selectedIndex > maxIndex) {
909
+ setSelectedIndex(maxIndex);
910
+ }
911
+ }
912
+ const targetPaneId = nextSelection?.pane && !nextSelection.pane.hidden
913
+ ? nextSelection.pane.paneId
914
+ : controlPaneId;
915
+ if (targetPaneId) {
690
916
  try {
691
- await TmuxService.getInstance().selectPane(controlPaneId);
917
+ await TmuxService.getInstance().selectPane(targetPaneId);
692
918
  }
693
919
  catch {
694
- // Ignore - control pane might not exist
920
+ // Ignore - the target pane might have closed during cleanup
695
921
  }
696
922
  }
697
923
  },
@@ -702,6 +928,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
702
928
  launchConfirmPopup: popupManager.launchConfirmPopup.bind(popupManager),
703
929
  launchChoicePopup: popupManager.launchChoicePopup.bind(popupManager),
704
930
  launchInputPopup: popupManager.launchInputPopup.bind(popupManager),
931
+ launchPRReviewPopup: popupManager.launchPRReviewPopup.bind(popupManager),
705
932
  launchProgressPopup: popupManager.launchProgressPopup.bind(popupManager),
706
933
  }
707
934
  : undefined,
@@ -816,12 +1043,14 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
816
1043
  setShowHooksPrompt(false);
817
1044
  setUseHooks(true);
818
1045
  settingsManager.updateSetting('useTmuxHooks', true, 'global');
1046
+ refreshDmuxSettings();
819
1047
  }
820
1048
  else if (input === 'n') {
821
1049
  // No - use polling
822
1050
  setShowHooksPrompt(false);
823
1051
  setUseHooks(false);
824
1052
  settingsManager.updateSetting('useTmuxHooks', false, 'global');
1053
+ refreshDmuxSettings();
825
1054
  }
826
1055
  else if (key.return) {
827
1056
  // Select current option
@@ -829,6 +1058,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
829
1058
  const selected = hooksPromptIndex === 0;
830
1059
  setUseHooks(selected);
831
1060
  settingsManager.updateSetting('useTmuxHooks', selected, 'global');
1061
+ refreshDmuxSettings();
832
1062
  }
833
1063
  }, { isActive: showHooksPrompt });
834
1064
  // Input handling - extracted to dedicated hook
@@ -856,6 +1086,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
856
1086
  projectSettings,
857
1087
  saveSettings,
858
1088
  settingsManager,
1089
+ refreshDmuxSettings,
859
1090
  popupManager,
860
1091
  actionSystem,
861
1092
  controlPaneId,
@@ -872,9 +1103,10 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
872
1103
  saveSidebarProjects,
873
1104
  loadPanes,
874
1105
  cleanExit,
875
- availableAgents,
1106
+ getAvailableAgentsForProject,
876
1107
  panesFile,
877
1108
  projectRoot: sessionProjectRoot,
1109
+ activeProjectRoot: selectedProjectRoot,
878
1110
  projectActionItems: projectActionLayout.actionItems,
879
1111
  findCardInDirection,
880
1112
  });
@@ -884,7 +1116,8 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
884
1116
  // - Normal mode calculation:
885
1117
  // - Base footer: 4 lines (marginTop + logs divider + logs line + keyboard shortcuts)
886
1118
  // - Footer tip: +1 line when footer tips are enabled
887
- // - Toast: +2 lines (toast message + marginBottom) if currentToast exists
1119
+ // - Toast (active): wrapped lines + header + marginBottom
1120
+ // - Toast (queued, transitioning): header + marginBottom (2 lines)
888
1121
  // - Debug info: +1 line if DEBUG_DMUX
889
1122
  // - Status line: +1 line if updateAvailable/currentBranch/debugMessage
890
1123
  // - Status messages: +1 line per active message
@@ -896,20 +1129,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
896
1129
  else {
897
1130
  footerLines = 0;
898
1131
  if (showFooterHelp) {
899
- footerLines = 4; // marginTop + logs divider + logs + shortcuts
1132
+ footerLines = 3; // logs divider + logs + shortcuts
900
1133
  if (currentFooterTip) {
901
1134
  footerLines += 1;
902
1135
  }
903
1136
  // Add toast notification (calculate wrapped lines + header)
904
1137
  if (currentToast) {
905
1138
  // Toast format: "✓ message" - icon (1) + space (1) + message
906
- const iconAndSpaceLength = 2;
907
- const toastTextLength = iconAndSpaceLength + currentToast.message.length;
1139
+ // Use stringWidth for CJK-aware display width calculation
1140
+ const iconAndSpaceWidth = 2;
1141
+ const toastDisplayWidth = iconAndSpaceWidth + stringWidth(currentToast.message);
908
1142
  // Available width is sidebar width (40) minus padding/margins (~2)
909
1143
  const availableWidth = SIDEBAR_WIDTH - 2;
910
- const wrappedLines = Math.ceil(toastTextLength / availableWidth);
1144
+ const wrappedLines = Math.ceil(toastDisplayWidth / availableWidth);
911
1145
  footerLines += wrappedLines + 1 + 1; // wrapped lines + header line + marginBottom
912
1146
  }
1147
+ else if (toastQueueLength > 0) {
1148
+ // When there are queued toasts but no current toast (transition state),
1149
+ // FooterHelp still renders the notification header + marginBottom
1150
+ footerLines += 1 + 1; // header line + marginBottom
1151
+ }
913
1152
  // Add debug info
914
1153
  if (process.env.DEBUG_DMUX) {
915
1154
  footerLines += 1;
@@ -928,9 +1167,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
928
1167
  }
929
1168
  }
930
1169
  const contentHeight = Math.max(terminalHeight - footerLines, 10);
931
- return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
1170
+ return (React.createElement(Box, { key: `theme-${selectedThemeName}-${themeRefreshNonce}`, flexDirection: "column", height: terminalHeight },
932
1171
  React.createElement(Box, { flexDirection: "column", height: contentHeight, overflow: "hidden" },
933
- React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, agentStatuses: agentStatuses, activeDevSourcePath: activeDevSourcePath, sidebarProjects: sidebarProjects, fallbackProjectRoot: projectRoot || process.cwd(), fallbackProjectName: projectName, isProjectBusy: isProjectHeaderBusy }),
1172
+ React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, activeProjectRoot: activeProjectRoot, isLoading: isLoading, themeName: selectedThemeName, projectThemeByRoot: projectThemeByRoot, agentStatuses: agentStatuses, activeDevSourcePath: activeDevSourcePath, sidebarProjects: sidebarProjects, fallbackProjectRoot: projectRoot || process.cwd(), fallbackProjectName: projectName, isProjectBusy: isProjectHeaderBusy }),
934
1173
  showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
935
1174
  showFileCopyPrompt && React.createElement(FileCopyPrompt, null),
936
1175
  showHooksPrompt && (React.createElement(TmuxHooksPromptDialog, { selectedIndex: hooksPromptIndex }))),