dmux 5.6.2 → 5.7.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 (257) hide show
  1. package/README.ja.md +93 -0
  2. package/README.md +7 -1
  3. package/dist/DmuxApp.d.ts.map +1 -1
  4. package/dist/DmuxApp.js +279 -40
  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 +93 -75
  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/duplicateAction.d.ts.map +1 -1
  14. package/dist/actions/implementations/duplicateAction.js +3 -1
  15. package/dist/actions/implementations/duplicateAction.js.map +1 -1
  16. package/dist/actions/implementations/index.d.ts +1 -0
  17. package/dist/actions/implementations/index.d.ts.map +1 -1
  18. package/dist/actions/implementations/index.js +1 -0
  19. package/dist/actions/implementations/index.js.map +1 -1
  20. package/dist/actions/implementations/mergeAction.d.ts.map +1 -1
  21. package/dist/actions/implementations/mergeAction.js +17 -9
  22. package/dist/actions/implementations/mergeAction.js.map +1 -1
  23. package/dist/actions/implementations/renameAction.d.ts +0 -9
  24. package/dist/actions/implementations/renameAction.d.ts.map +1 -1
  25. package/dist/actions/implementations/renameAction.js +77 -13
  26. package/dist/actions/implementations/renameAction.js.map +1 -1
  27. package/dist/actions/implementations/toggleAutopilotAction.d.ts.map +1 -1
  28. package/dist/actions/implementations/toggleAutopilotAction.js +3 -1
  29. package/dist/actions/implementations/toggleAutopilotAction.js.map +1 -1
  30. package/dist/actions/implementations/viewAction.d.ts.map +1 -1
  31. package/dist/actions/implementations/viewAction.js +4 -2
  32. package/dist/actions/implementations/viewAction.js.map +1 -1
  33. package/dist/actions/index.d.ts.map +1 -1
  34. package/dist/actions/index.js +2 -0
  35. package/dist/actions/index.js.map +1 -1
  36. package/dist/actions/merge/issueHandlers/mergeConflictHandler.d.ts.map +1 -1
  37. package/dist/actions/merge/issueHandlers/mergeConflictHandler.js +3 -1
  38. package/dist/actions/merge/issueHandlers/mergeConflictHandler.js.map +1 -1
  39. package/dist/actions/paneActions.d.ts +1 -1
  40. package/dist/actions/paneActions.d.ts.map +1 -1
  41. package/dist/actions/paneActions.js +1 -1
  42. package/dist/actions/paneActions.js.map +1 -1
  43. package/dist/actions/types.d.ts +11 -2
  44. package/dist/actions/types.d.ts.map +1 -1
  45. package/dist/actions/types.js +9 -0
  46. package/dist/actions/types.js.map +1 -1
  47. package/dist/components/inputs/CleanTextInput.d.ts +1 -0
  48. package/dist/components/inputs/CleanTextInput.d.ts.map +1 -1
  49. package/dist/components/inputs/CleanTextInput.js +40 -42
  50. package/dist/components/inputs/CleanTextInput.js.map +1 -1
  51. package/dist/components/panes/PaneCard.d.ts +3 -1
  52. package/dist/components/panes/PaneCard.d.ts.map +1 -1
  53. package/dist/components/panes/PaneCard.js +18 -5
  54. package/dist/components/panes/PaneCard.js.map +1 -1
  55. package/dist/components/panes/PanesGrid.d.ts +4 -1
  56. package/dist/components/panes/PanesGrid.d.ts.map +1 -1
  57. package/dist/components/panes/PanesGrid.js +27 -15
  58. package/dist/components/panes/PanesGrid.js.map +1 -1
  59. package/dist/components/popups/config.d.ts +4 -4
  60. package/dist/components/popups/config.js +6 -6
  61. package/dist/components/popups/config.js.map +1 -1
  62. package/dist/components/popups/inputPopup.js +3 -3
  63. package/dist/components/popups/inputPopup.js.map +1 -1
  64. package/dist/components/popups/logsPopup.js +2 -1
  65. package/dist/components/popups/logsPopup.js.map +1 -1
  66. package/dist/components/popups/prReviewPopup.d.ts +12 -0
  67. package/dist/components/popups/prReviewPopup.d.ts.map +1 -0
  68. package/dist/components/popups/prReviewPopup.js +278 -0
  69. package/dist/components/popups/prReviewPopup.js.map +1 -0
  70. package/dist/components/popups/reopenWorktreePopup.js +1 -1
  71. package/dist/components/popups/reopenWorktreePopup.js.map +1 -1
  72. package/dist/components/popups/settingsPopup.js +72 -6
  73. package/dist/components/popups/settingsPopup.js.map +1 -1
  74. package/dist/components/ui/FooterHelp.d.ts.map +1 -1
  75. package/dist/components/ui/FooterHelp.js +5 -4
  76. package/dist/components/ui/FooterHelp.js.map +1 -1
  77. package/dist/hooks/useActionSystem.d.ts +12 -2
  78. package/dist/hooks/useActionSystem.d.ts.map +1 -1
  79. package/dist/hooks/useActionSystem.js +19 -1
  80. package/dist/hooks/useActionSystem.js.map +1 -1
  81. package/dist/hooks/useInputHandling.d.ts +2 -1
  82. package/dist/hooks/useInputHandling.d.ts.map +1 -1
  83. package/dist/hooks/useInputHandling.js +100 -31
  84. package/dist/hooks/useInputHandling.js.map +1 -1
  85. package/dist/hooks/usePaneLoading.d.ts.map +1 -1
  86. package/dist/hooks/usePaneLoading.js +29 -4
  87. package/dist/hooks/usePaneLoading.js.map +1 -1
  88. package/dist/hooks/usePaneSync.d.ts +2 -2
  89. package/dist/hooks/usePaneSync.d.ts.map +1 -1
  90. package/dist/hooks/usePaneSync.js +10 -7
  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 +10 -2
  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 +35 -8
  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.js +1 -1
  103. package/dist/services/DmuxFocusService.d.ts.map +1 -1
  104. package/dist/services/DmuxFocusService.js +19 -14
  105. package/dist/services/DmuxFocusService.js.map +1 -1
  106. package/dist/services/PaneAnalyzer.d.ts.map +1 -1
  107. package/dist/services/PaneAnalyzer.js +2 -1
  108. package/dist/services/PaneAnalyzer.js.map +1 -1
  109. package/dist/services/PaneEventService.d.ts +11 -0
  110. package/dist/services/PaneEventService.d.ts.map +1 -1
  111. package/dist/services/PaneEventService.js +31 -3
  112. package/dist/services/PaneEventService.js.map +1 -1
  113. package/dist/services/PaneWorkerManager.d.ts.map +1 -1
  114. package/dist/services/PaneWorkerManager.js +1 -0
  115. package/dist/services/PaneWorkerManager.js.map +1 -1
  116. package/dist/services/PopupManager.d.ts +17 -5
  117. package/dist/services/PopupManager.d.ts.map +1 -1
  118. package/dist/services/PopupManager.js +143 -32
  119. package/dist/services/PopupManager.js.map +1 -1
  120. package/dist/services/StatusDetector.d.ts +1 -0
  121. package/dist/services/StatusDetector.d.ts.map +1 -1
  122. package/dist/services/StatusDetector.js +47 -2
  123. package/dist/services/StatusDetector.js.map +1 -1
  124. package/dist/services/TmuxHookManager.d.ts.map +1 -1
  125. package/dist/services/TmuxHookManager.js +3 -2
  126. package/dist/services/TmuxHookManager.js.map +1 -1
  127. package/dist/services/TmuxService.d.ts +11 -0
  128. package/dist/services/TmuxService.d.ts.map +1 -1
  129. package/dist/services/TmuxService.js +27 -1
  130. package/dist/services/TmuxService.js.map +1 -1
  131. package/dist/theme/colors.d.ts +24 -10
  132. package/dist/theme/colors.d.ts.map +1 -1
  133. package/dist/theme/colors.js +143 -19
  134. package/dist/theme/colors.js.map +1 -1
  135. package/dist/theme/themePalette.d.ts +6 -0
  136. package/dist/theme/themePalette.d.ts.map +1 -0
  137. package/dist/theme/themePalette.js +18 -0
  138. package/dist/theme/themePalette.js.map +1 -0
  139. package/dist/types.d.ts +9 -0
  140. package/dist/types.d.ts.map +1 -1
  141. package/dist/utils/agentLaunch.d.ts +2 -0
  142. package/dist/utils/agentLaunch.d.ts.map +1 -1
  143. package/dist/utils/agentLaunch.js +10 -9
  144. package/dist/utils/agentLaunch.js.map +1 -1
  145. package/dist/utils/aiMerge.d.ts +4 -0
  146. package/dist/utils/aiMerge.d.ts.map +1 -1
  147. package/dist/utils/aiMerge.js +7 -4
  148. package/dist/utils/aiMerge.js.map +1 -1
  149. package/dist/utils/asciiArt.d.ts.map +1 -1
  150. package/dist/utils/asciiArt.js +2 -1
  151. package/dist/utils/asciiArt.js.map +1 -1
  152. package/dist/utils/attachAgent.d.ts.map +1 -1
  153. package/dist/utils/attachAgent.js +20 -1
  154. package/dist/utils/attachAgent.js.map +1 -1
  155. package/dist/utils/codexHooks.d.ts +25 -0
  156. package/dist/utils/codexHooks.d.ts.map +1 -0
  157. package/dist/utils/codexHooks.js +131 -0
  158. package/dist/utils/codexHooks.js.map +1 -0
  159. package/dist/utils/conflictResolutionPane.d.ts.map +1 -1
  160. package/dist/utils/conflictResolutionPane.js +6 -2
  161. package/dist/utils/conflictResolutionPane.js.map +1 -1
  162. package/dist/utils/generated-agents-doc.d.ts +1 -1
  163. package/dist/utils/generated-agents-doc.js +1 -1
  164. package/dist/utils/githubPullRequest.d.ts +18 -0
  165. package/dist/utils/githubPullRequest.d.ts.map +1 -0
  166. package/dist/utils/githubPullRequest.js +196 -0
  167. package/dist/utils/githubPullRequest.js.map +1 -0
  168. package/dist/utils/input.d.ts +7 -6
  169. package/dist/utils/input.d.ts.map +1 -1
  170. package/dist/utils/input.js +47 -24
  171. package/dist/utils/input.js.map +1 -1
  172. package/dist/utils/mergeTargets.d.ts +1 -1
  173. package/dist/utils/mergeTargets.d.ts.map +1 -1
  174. package/dist/utils/mergeTargets.js +12 -8
  175. package/dist/utils/mergeTargets.js.map +1 -1
  176. package/dist/utils/mergeValidation.d.ts.map +1 -1
  177. package/dist/utils/mergeValidation.js +45 -11
  178. package/dist/utils/mergeValidation.js.map +1 -1
  179. package/dist/utils/paneColors.d.ts +7 -0
  180. package/dist/utils/paneColors.d.ts.map +1 -0
  181. package/dist/utils/paneColors.js +42 -0
  182. package/dist/utils/paneColors.js.map +1 -0
  183. package/dist/utils/paneCreation.d.ts.map +1 -1
  184. package/dist/utils/paneCreation.js +110 -36
  185. package/dist/utils/paneCreation.js.map +1 -1
  186. package/dist/utils/paneRebinding.d.ts +1 -1
  187. package/dist/utils/paneRebinding.js +1 -1
  188. package/dist/utils/paneTitle.d.ts +8 -1
  189. package/dist/utils/paneTitle.d.ts.map +1 -1
  190. package/dist/utils/paneTitle.js +58 -10
  191. package/dist/utils/paneTitle.js.map +1 -1
  192. package/dist/utils/paneTitlePrefix.d.ts +8 -0
  193. package/dist/utils/paneTitlePrefix.d.ts.map +1 -0
  194. package/dist/utils/paneTitlePrefix.js +20 -0
  195. package/dist/utils/paneTitlePrefix.js.map +1 -0
  196. package/dist/utils/popup.d.ts +1 -0
  197. package/dist/utils/popup.d.ts.map +1 -1
  198. package/dist/utils/popup.js +9 -2
  199. package/dist/utils/popup.js.map +1 -1
  200. package/dist/utils/prSummary.d.ts +34 -0
  201. package/dist/utils/prSummary.d.ts.map +1 -0
  202. package/dist/utils/prSummary.js +130 -0
  203. package/dist/utils/prSummary.js.map +1 -0
  204. package/dist/utils/projectActions.d.ts +6 -0
  205. package/dist/utils/projectActions.d.ts.map +1 -1
  206. package/dist/utils/projectActions.js +46 -0
  207. package/dist/utils/projectActions.js.map +1 -1
  208. package/dist/utils/reopenWorktree.d.ts.map +1 -1
  209. package/dist/utils/reopenWorktree.js +40 -7
  210. package/dist/utils/reopenWorktree.js.map +1 -1
  211. package/dist/utils/resumeBranches.d.ts.map +1 -1
  212. package/dist/utils/resumeBranches.js +9 -3
  213. package/dist/utils/resumeBranches.js.map +1 -1
  214. package/dist/utils/settingsManager.d.ts +11 -3
  215. package/dist/utils/settingsManager.d.ts.map +1 -1
  216. package/dist/utils/settingsManager.js +96 -21
  217. package/dist/utils/settingsManager.js.map +1 -1
  218. package/dist/utils/sidebarProjects.d.ts +10 -3
  219. package/dist/utils/sidebarProjects.d.ts.map +1 -1
  220. package/dist/utils/sidebarProjects.js +125 -10
  221. package/dist/utils/sidebarProjects.js.map +1 -1
  222. package/dist/utils/tmux.d.ts +4 -0
  223. package/dist/utils/tmux.d.ts.map +1 -1
  224. package/dist/utils/tmux.js +8 -0
  225. package/dist/utils/tmux.js.map +1 -1
  226. package/dist/utils/tmuxHookCommands.d.ts +6 -0
  227. package/dist/utils/tmuxHookCommands.d.ts.map +1 -1
  228. package/dist/utils/tmuxHookCommands.js +12 -0
  229. package/dist/utils/tmuxHookCommands.js.map +1 -1
  230. package/dist/utils/tmuxSendKeys.d.ts +2 -0
  231. package/dist/utils/tmuxSendKeys.d.ts.map +1 -0
  232. package/dist/utils/tmuxSendKeys.js +8 -0
  233. package/dist/utils/tmuxSendKeys.js.map +1 -0
  234. package/dist/utils/toastLayout.d.ts +10 -0
  235. package/dist/utils/toastLayout.d.ts.map +1 -0
  236. package/dist/utils/toastLayout.js +24 -0
  237. package/dist/utils/toastLayout.js.map +1 -0
  238. package/dist/utils/welcomePane.d.ts +5 -1
  239. package/dist/utils/welcomePane.d.ts.map +1 -1
  240. package/dist/utils/welcomePane.js +33 -3
  241. package/dist/utils/welcomePane.js.map +1 -1
  242. package/dist/utils/welcomePaneManager.d.ts +3 -1
  243. package/dist/utils/welcomePaneManager.d.ts.map +1 -1
  244. package/dist/utils/welcomePaneManager.js +40 -2
  245. package/dist/utils/welcomePaneManager.js.map +1 -1
  246. package/dist/utils/worktreeMetadata.d.ts +1 -0
  247. package/dist/utils/worktreeMetadata.d.ts.map +1 -1
  248. package/dist/utils/worktreeMetadata.js +17 -1
  249. package/dist/utils/worktreeMetadata.js.map +1 -1
  250. package/dist/workers/PaneWorker.js +77 -0
  251. package/dist/workers/PaneWorker.js.map +1 -1
  252. package/dist/workers/WorkerMessages.d.ts +9 -1
  253. package/dist/workers/WorkerMessages.d.ts.map +1 -1
  254. package/dist/workers/WorkerMessages.js.map +1 -1
  255. package/dist/workers/panePollingWorker.js +33 -2
  256. package/dist/workers/panePollingWorker.js.map +1 -1
  257. 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";
@@ -39,17 +40,25 @@ import { getPaneBranchName } from "./utils/git.js";
39
40
  import { getGitStatus } from "./utils/mergeValidation.js";
40
41
  import { createMergeTargetChain } from "./utils/mergeTargets.js";
41
42
  import { claimProcessShutdown } from "./utils/processShutdown.js";
43
+ import { getPaneDisplayName } from "./utils/paneTitle.js";
42
44
  import { FOOTER_TIP_ROTATION_INTERVAL, getFooterTips, getNextFooterTipIndex, getRandomFooterTipIndex, } from "./utils/footerTips.js";
43
45
  const __filename = fileURLToPath(import.meta.url);
44
46
  const __dirname = dirname(__filename);
47
+ const ACTIVE_PANE_SYNC_INTERVAL_MS = 125;
45
48
  import PanesGrid from "./components/panes/PanesGrid.js";
46
49
  import CommandPromptDialog from "./components/dialogs/CommandPromptDialog.js";
47
50
  import FileCopyPrompt from "./components/ui/FileCopyPrompt.js";
48
51
  import FooterHelp from "./components/ui/FooterHelp.js";
49
52
  import TmuxHooksPromptDialog from "./components/dialogs/TmuxHooksPromptDialog.js";
50
53
  import { PaneEventService } from "./services/PaneEventService.js";
51
- import { buildProjectActionLayout, buildVisualNavigationRows, buildGroupStartRows, getProjectActionByIndex, } from "./utils/projectActions.js";
54
+ import { buildProjectActionLayout, buildVisualNavigationRows, buildGroupStartRows, getProjectActionByIndex, resolveSelectionAfterPaneClose, } from "./utils/projectActions.js";
52
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";
53
62
  const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoot, autoUpdater, controlPaneId, }) => {
54
63
  const { stdout } = useStdout();
55
64
  const terminalHeight = stdout?.rows || 40;
@@ -57,13 +66,19 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
57
66
  const sessionProjectRoot = projectRoot || process.cwd();
58
67
  /* panes state moved to usePanes */
59
68
  const [selectedIndex, setSelectedIndex] = useState(0);
69
+ const [focusedPaneId, setFocusedPaneId] = useState(null);
60
70
  const { statusMessage, setStatusMessage } = useStatusMessages();
61
71
  const [isCreatingPane, setIsCreatingPane] = useState(false);
62
72
  const { trackProjectActivity, isProjectBusy: isTrackedProjectBusy, } = useProjectActivity(sessionProjectRoot);
63
73
  // Settings state
64
74
  const [settingsManager] = useState(() => new SettingsManager(projectRoot));
65
75
  const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
66
- 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);
67
82
  // Dialog state management
68
83
  const dialogState = useDialogState();
69
84
  const { showCommandPrompt, setShowCommandPrompt, commandInput, setCommandInput, showFileCopyPrompt, setShowFileCopyPrompt, currentCommandType, setCurrentCommandType, runningCommand, setRunningCommand, quitConfirmMode, setQuitConfirmMode, } = dialogState;
@@ -77,6 +92,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
77
92
  // Agent selection is settings-driven.
78
93
  // Installation checks are performed lazily in the Enabled Agents settings popup.
79
94
  const availableAgents = resolveEnabledAgentsSelection(settings.enabledAgents);
95
+ const getAvailableAgentsForProject = (targetProjectRoot = selectedProjectRoot) => resolveEnabledAgentsSelection(new SettingsManager(targetProjectRoot).getSettings().enabledAgents);
80
96
  const footerTips = useMemo(() => getFooterTips(isDevMode), [isDevMode]);
81
97
  const showFooterTips = settings.showFooterTips !== false && footerTips.length > 0;
82
98
  const [footerTipIndex, setFooterTipIndex] = useState(() => getRandomFooterTipIndex(footerTips.length));
@@ -132,12 +148,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
132
148
  };
133
149
  }, []);
134
150
  // Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
135
- 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);
136
152
  // Check for tmux hooks preference on startup
137
153
  useEffect(() => {
138
154
  const checkHooksPreference = async () => {
139
155
  // Check if user already has a preference
140
- const settings = settingsManager.getSettings();
156
+ const settings = new SettingsManager(sessionProjectRoot).getSettings();
141
157
  if (settings.useTmuxHooks !== undefined) {
142
158
  // User has already decided
143
159
  setUseHooks(settings.useTmuxHooks);
@@ -152,6 +168,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
152
168
  setUseHooks(true);
153
169
  // Save the preference
154
170
  settingsManager.updateSetting('useTmuxHooks', true, 'global');
171
+ refreshDmuxSettings();
155
172
  }
156
173
  else {
157
174
  // Need to ask user - show prompt
@@ -346,18 +363,163 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
346
363
  // getPanePositions moved to utils/tmux
347
364
  const activeDevSourcePath = isDevMode ? process.cwd() : undefined;
348
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]);
349
375
  const selectedProjectRoot = useMemo(() => {
350
- const selectedPane = selectedIndex < panes.length ? panes[selectedIndex] : undefined;
351
376
  if (selectedPane) {
352
377
  return getPaneProjectRoot(selectedPane, sessionProjectRoot);
353
378
  }
354
379
  return (getProjectActionByIndex(projectActionLayout.actionItems, selectedIndex)?.projectRoot
355
380
  || sessionProjectRoot);
356
- }, [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
+ };
357
411
  const navigationRows = useMemo(() => isLoading
358
412
  ? projectActionLayout.groups.flatMap((group) => group.panes.map((entry) => [entry.index]))
359
413
  : buildVisualNavigationRows(projectActionLayout), [isLoading, projectActionLayout]);
360
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
+ ]);
361
523
  useEffect(() => {
362
524
  const maxIndex = Math.max(0, projectActionLayout.totalItems - 1);
363
525
  if (selectedIndex > maxIndex) {
@@ -367,14 +529,59 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
367
529
  // Navigation logic moved to hook
368
530
  const { getCardGridPosition, findCardInDirection } = useNavigation(navigationRows, groupStartRows);
369
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]);
370
576
  // savePanes moved to usePanes
371
577
  // applySmartLayout moved to utils/tmux
372
578
  // Helper function to handle agent choice and pane creation
373
579
  const selectAgentsForPaneCreation = async (targetProjectRoot) => {
374
- if (availableAgents.length === 0) {
580
+ const targetRoot = targetProjectRoot || selectedProjectRoot;
581
+ if (getAvailableAgentsForProject(targetRoot).length === 0) {
375
582
  return [];
376
583
  }
377
- const selectedAgents = await popupManager.launchAgentChoicePopup(targetProjectRoot || selectedProjectRoot);
584
+ const selectedAgents = await popupManager.launchAgentChoicePopup(targetRoot);
378
585
  if (selectedAgents === null) {
379
586
  return null;
380
587
  }
@@ -433,12 +640,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
433
640
  if (createdCount > 0) {
434
641
  return {
435
642
  type: "success",
436
- message: `Created ${createdCount} sub-worktree${createdCount === 1 ? "" : "s"} from ${parentPane.slug}`,
643
+ message: `Created ${createdCount} sub-worktree${createdCount === 1 ? "" : "s"} from ${getPaneDisplayName(parentPane)}`,
437
644
  };
438
645
  }
439
646
  return {
440
647
  type: "error",
441
- message: `Failed to create a sub-worktree from ${parentPane.slug}`,
648
+ message: `Failed to create a sub-worktree from ${getPaneDisplayName(parentPane)}`,
442
649
  dismissable: true,
443
650
  };
444
651
  };
@@ -453,7 +660,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
453
660
  const branchFromDirtyResult = {
454
661
  type: "choice",
455
662
  title: "Parent Worktree Has Uncommitted Changes",
456
- message: `"${parentPane.slug}" has uncommitted changes. Commit them before creating a sub-worktree.`,
663
+ message: `"${getPaneDisplayName(parentPane)}" has uncommitted changes. Commit them before creating a sub-worktree.`,
457
664
  options: [
458
665
  {
459
666
  id: "commit_automatic",
@@ -513,7 +720,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
513
720
  const reopenProjectRoot = targetProjectRoot || projectRoot || process.cwd();
514
721
  let selectedAgent;
515
722
  if (!candidate.path) {
516
- if (availableAgents.length === 0) {
723
+ if (getAvailableAgentsForProject(reopenProjectRoot).length === 0) {
517
724
  setStatusMessage("No enabled agents available for opening this branch");
518
725
  setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
519
726
  return;
@@ -549,7 +756,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
549
756
  const updatedPanes = [...panes, result.pane];
550
757
  await savePanes(updatedPanes);
551
758
  await loadPanes();
552
- setStatusMessage(`${candidate.path ? "Reopened" : "Opened"} ${label}`);
759
+ setStatusMessage(`${candidate.path ? "Reopened" : "Opened"} ${getPaneDisplayName(result.pane)}`);
553
760
  setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
554
761
  }
555
762
  catch (error) {
@@ -590,7 +797,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
590
797
  await trackProjectActivity(() => restartDevSessionAtSource(nextSource.nextSourcePath), sessionProjectRoot);
591
798
  return;
592
799
  }
593
- setStatusMessage(`Switching source to "${pane.slug}"...`);
800
+ setStatusMessage(`Switching source to "${getPaneDisplayName(pane)}"...`);
594
801
  await trackProjectActivity(() => restartDevSessionAtSource(nextSource.nextSourcePath), paneProjectRoot);
595
802
  };
596
803
  // Helper function to handle action results recursively
@@ -631,7 +838,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
631
838
  else if (result.type === "input") {
632
839
  if (!result.onSubmit)
633
840
  return;
634
- 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);
635
842
  if (inputValue !== null) {
636
843
  const nextResult = await trackProjectActivity(() => result.onSubmit(inputValue), selectedProjectRoot);
637
844
  // Recursively handle nested results
@@ -640,6 +847,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
640
847
  }
641
848
  }
642
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
+ }
643
870
  else if (result.type === "navigation") {
644
871
  // Navigate to target pane if specified
645
872
  if (result.targetPaneId) {
@@ -672,25 +899,25 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
672
899
  projectName,
673
900
  defaultProjectRoot: sessionProjectRoot,
674
901
  onPaneRemove: async (paneId) => {
675
- // Mark pane as closing to prevent race condition with worker
676
- await lifecycleManager.beginClose(paneId, 'user requested');
677
- // Adjust selectedIndex before removing from list
678
- const removedIndex = panes.findIndex((p) => p.paneId === paneId);
679
- if (removedIndex >= 0 && selectedIndex >= panes.length - 1) {
680
- setSelectedIndex(Math.max(0, panes.length - 2));
681
- }
682
- // Remove from panes list
683
- const updatedPanes = panes.filter((p) => p.paneId !== paneId);
684
- savePanes(updatedPanes);
685
- // Mark close as completed (no more lock needed)
686
- await lifecycleManager.completeClose(paneId);
687
- // Return focus to control pane
688
- 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) {
689
916
  try {
690
- await TmuxService.getInstance().selectPane(controlPaneId);
917
+ await TmuxService.getInstance().selectPane(targetPaneId);
691
918
  }
692
919
  catch {
693
- // Ignore - control pane might not exist
920
+ // Ignore - the target pane might have closed during cleanup
694
921
  }
695
922
  }
696
923
  },
@@ -701,6 +928,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
701
928
  launchConfirmPopup: popupManager.launchConfirmPopup.bind(popupManager),
702
929
  launchChoicePopup: popupManager.launchChoicePopup.bind(popupManager),
703
930
  launchInputPopup: popupManager.launchInputPopup.bind(popupManager),
931
+ launchPRReviewPopup: popupManager.launchPRReviewPopup.bind(popupManager),
704
932
  launchProgressPopup: popupManager.launchProgressPopup.bind(popupManager),
705
933
  }
706
934
  : undefined,
@@ -815,12 +1043,14 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
815
1043
  setShowHooksPrompt(false);
816
1044
  setUseHooks(true);
817
1045
  settingsManager.updateSetting('useTmuxHooks', true, 'global');
1046
+ refreshDmuxSettings();
818
1047
  }
819
1048
  else if (input === 'n') {
820
1049
  // No - use polling
821
1050
  setShowHooksPrompt(false);
822
1051
  setUseHooks(false);
823
1052
  settingsManager.updateSetting('useTmuxHooks', false, 'global');
1053
+ refreshDmuxSettings();
824
1054
  }
825
1055
  else if (key.return) {
826
1056
  // Select current option
@@ -828,6 +1058,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
828
1058
  const selected = hooksPromptIndex === 0;
829
1059
  setUseHooks(selected);
830
1060
  settingsManager.updateSetting('useTmuxHooks', selected, 'global');
1061
+ refreshDmuxSettings();
831
1062
  }
832
1063
  }, { isActive: showHooksPrompt });
833
1064
  // Input handling - extracted to dedicated hook
@@ -855,6 +1086,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
855
1086
  projectSettings,
856
1087
  saveSettings,
857
1088
  settingsManager,
1089
+ refreshDmuxSettings,
858
1090
  popupManager,
859
1091
  actionSystem,
860
1092
  controlPaneId,
@@ -871,7 +1103,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
871
1103
  saveSidebarProjects,
872
1104
  loadPanes,
873
1105
  cleanExit,
874
- availableAgents,
1106
+ getAvailableAgentsForProject,
875
1107
  panesFile,
876
1108
  projectRoot: sessionProjectRoot,
877
1109
  projectActionItems: projectActionLayout.actionItems,
@@ -883,7 +1115,8 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
883
1115
  // - Normal mode calculation:
884
1116
  // - Base footer: 4 lines (marginTop + logs divider + logs line + keyboard shortcuts)
885
1117
  // - Footer tip: +1 line when footer tips are enabled
886
- // - Toast: +2 lines (toast message + marginBottom) if currentToast exists
1118
+ // - Toast (active): wrapped lines + header + marginBottom
1119
+ // - Toast (queued, transitioning): header + marginBottom (2 lines)
887
1120
  // - Debug info: +1 line if DEBUG_DMUX
888
1121
  // - Status line: +1 line if updateAvailable/currentBranch/debugMessage
889
1122
  // - Status messages: +1 line per active message
@@ -895,20 +1128,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
895
1128
  else {
896
1129
  footerLines = 0;
897
1130
  if (showFooterHelp) {
898
- footerLines = 4; // marginTop + logs divider + logs + shortcuts
1131
+ footerLines = 3; // logs divider + logs + shortcuts
899
1132
  if (currentFooterTip) {
900
1133
  footerLines += 1;
901
1134
  }
902
1135
  // Add toast notification (calculate wrapped lines + header)
903
1136
  if (currentToast) {
904
1137
  // Toast format: "✓ message" - icon (1) + space (1) + message
905
- const iconAndSpaceLength = 2;
906
- const toastTextLength = iconAndSpaceLength + currentToast.message.length;
1138
+ // Use stringWidth for CJK-aware display width calculation
1139
+ const iconAndSpaceWidth = 2;
1140
+ const toastDisplayWidth = iconAndSpaceWidth + stringWidth(currentToast.message);
907
1141
  // Available width is sidebar width (40) minus padding/margins (~2)
908
1142
  const availableWidth = SIDEBAR_WIDTH - 2;
909
- const wrappedLines = Math.ceil(toastTextLength / availableWidth);
1143
+ const wrappedLines = Math.ceil(toastDisplayWidth / availableWidth);
910
1144
  footerLines += wrappedLines + 1 + 1; // wrapped lines + header line + marginBottom
911
1145
  }
1146
+ else if (toastQueueLength > 0) {
1147
+ // When there are queued toasts but no current toast (transition state),
1148
+ // FooterHelp still renders the notification header + marginBottom
1149
+ footerLines += 1 + 1; // header line + marginBottom
1150
+ }
912
1151
  // Add debug info
913
1152
  if (process.env.DEBUG_DMUX) {
914
1153
  footerLines += 1;
@@ -927,9 +1166,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
927
1166
  }
928
1167
  }
929
1168
  const contentHeight = Math.max(terminalHeight - footerLines, 10);
930
- return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
1169
+ return (React.createElement(Box, { key: `theme-${selectedThemeName}-${themeRefreshNonce}`, flexDirection: "column", height: terminalHeight },
931
1170
  React.createElement(Box, { flexDirection: "column", height: contentHeight, overflow: "hidden" },
932
- React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, agentStatuses: agentStatuses, activeDevSourcePath: activeDevSourcePath, sidebarProjects: sidebarProjects, fallbackProjectRoot: projectRoot || process.cwd(), fallbackProjectName: projectName, isProjectBusy: isProjectHeaderBusy }),
1171
+ 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 }),
933
1172
  showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
934
1173
  showFileCopyPrompt && React.createElement(FileCopyPrompt, null),
935
1174
  showHooksPrompt && (React.createElement(TmuxHooksPromptDialog, { selectedIndex: hooksPromptIndex }))),