dmux 5.6.3 → 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 (216) 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 +273 -35
  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/panes/PaneCard.d.ts +3 -1
  35. package/dist/components/panes/PaneCard.d.ts.map +1 -1
  36. package/dist/components/panes/PaneCard.js +14 -4
  37. package/dist/components/panes/PaneCard.js.map +1 -1
  38. package/dist/components/panes/PanesGrid.d.ts +4 -1
  39. package/dist/components/panes/PanesGrid.d.ts.map +1 -1
  40. package/dist/components/panes/PanesGrid.js +27 -15
  41. package/dist/components/panes/PanesGrid.js.map +1 -1
  42. package/dist/components/popups/config.d.ts +4 -4
  43. package/dist/components/popups/config.js +6 -6
  44. package/dist/components/popups/config.js.map +1 -1
  45. package/dist/components/popups/inputPopup.js +3 -3
  46. package/dist/components/popups/inputPopup.js.map +1 -1
  47. package/dist/components/popups/prReviewPopup.d.ts +12 -0
  48. package/dist/components/popups/prReviewPopup.d.ts.map +1 -0
  49. package/dist/components/popups/prReviewPopup.js +278 -0
  50. package/dist/components/popups/prReviewPopup.js.map +1 -0
  51. package/dist/components/popups/reopenWorktreePopup.js +1 -1
  52. package/dist/components/popups/reopenWorktreePopup.js.map +1 -1
  53. package/dist/components/popups/settingsPopup.js +72 -6
  54. package/dist/components/popups/settingsPopup.js.map +1 -1
  55. package/dist/components/ui/FooterHelp.d.ts.map +1 -1
  56. package/dist/components/ui/FooterHelp.js +5 -4
  57. package/dist/components/ui/FooterHelp.js.map +1 -1
  58. package/dist/hooks/useActionSystem.d.ts +12 -2
  59. package/dist/hooks/useActionSystem.d.ts.map +1 -1
  60. package/dist/hooks/useActionSystem.js +19 -1
  61. package/dist/hooks/useActionSystem.js.map +1 -1
  62. package/dist/hooks/useInputHandling.d.ts +2 -1
  63. package/dist/hooks/useInputHandling.d.ts.map +1 -1
  64. package/dist/hooks/useInputHandling.js +84 -18
  65. package/dist/hooks/useInputHandling.js.map +1 -1
  66. package/dist/hooks/usePaneLoading.d.ts.map +1 -1
  67. package/dist/hooks/usePaneLoading.js +28 -3
  68. package/dist/hooks/usePaneLoading.js.map +1 -1
  69. package/dist/hooks/usePaneSync.d.ts.map +1 -1
  70. package/dist/hooks/usePaneSync.js +8 -5
  71. package/dist/hooks/usePaneSync.js.map +1 -1
  72. package/dist/hooks/usePanes.d.ts.map +1 -1
  73. package/dist/hooks/usePanes.js +8 -1
  74. package/dist/hooks/usePanes.js.map +1 -1
  75. package/dist/hooks/useShellDetection.d.ts.map +1 -1
  76. package/dist/hooks/useShellDetection.js +7 -1
  77. package/dist/hooks/useShellDetection.js.map +1 -1
  78. package/dist/index.js +35 -9
  79. package/dist/index.js.map +1 -1
  80. package/dist/panes/decorative-pane.js +57 -27
  81. package/dist/panes/decorative-pane.js.map +1 -1
  82. package/dist/services/DmuxFocusService.d.ts.map +1 -1
  83. package/dist/services/DmuxFocusService.js +19 -14
  84. package/dist/services/DmuxFocusService.js.map +1 -1
  85. package/dist/services/PaneEventService.d.ts +11 -0
  86. package/dist/services/PaneEventService.d.ts.map +1 -1
  87. package/dist/services/PaneEventService.js +31 -3
  88. package/dist/services/PaneEventService.js.map +1 -1
  89. package/dist/services/PaneWorkerManager.d.ts.map +1 -1
  90. package/dist/services/PaneWorkerManager.js +1 -0
  91. package/dist/services/PaneWorkerManager.js.map +1 -1
  92. package/dist/services/PopupManager.d.ts +17 -5
  93. package/dist/services/PopupManager.d.ts.map +1 -1
  94. package/dist/services/PopupManager.js +140 -30
  95. package/dist/services/PopupManager.js.map +1 -1
  96. package/dist/services/StatusDetector.d.ts +1 -0
  97. package/dist/services/StatusDetector.d.ts.map +1 -1
  98. package/dist/services/StatusDetector.js +44 -0
  99. package/dist/services/StatusDetector.js.map +1 -1
  100. package/dist/services/TmuxHookManager.d.ts.map +1 -1
  101. package/dist/services/TmuxHookManager.js +3 -2
  102. package/dist/services/TmuxHookManager.js.map +1 -1
  103. package/dist/services/TmuxService.d.ts +7 -0
  104. package/dist/services/TmuxService.d.ts.map +1 -1
  105. package/dist/services/TmuxService.js +15 -1
  106. package/dist/services/TmuxService.js.map +1 -1
  107. package/dist/theme/colors.d.ts +24 -10
  108. package/dist/theme/colors.d.ts.map +1 -1
  109. package/dist/theme/colors.js +143 -19
  110. package/dist/theme/colors.js.map +1 -1
  111. package/dist/theme/themePalette.d.ts +6 -0
  112. package/dist/theme/themePalette.d.ts.map +1 -0
  113. package/dist/theme/themePalette.js +18 -0
  114. package/dist/theme/themePalette.js.map +1 -0
  115. package/dist/types.d.ts +7 -0
  116. package/dist/types.d.ts.map +1 -1
  117. package/dist/utils/agentLaunch.d.ts +2 -0
  118. package/dist/utils/agentLaunch.d.ts.map +1 -1
  119. package/dist/utils/agentLaunch.js +10 -9
  120. package/dist/utils/agentLaunch.js.map +1 -1
  121. package/dist/utils/aiMerge.d.ts +4 -0
  122. package/dist/utils/aiMerge.d.ts.map +1 -1
  123. package/dist/utils/aiMerge.js +7 -4
  124. package/dist/utils/aiMerge.js.map +1 -1
  125. package/dist/utils/asciiArt.d.ts.map +1 -1
  126. package/dist/utils/asciiArt.js +2 -1
  127. package/dist/utils/asciiArt.js.map +1 -1
  128. package/dist/utils/attachAgent.d.ts.map +1 -1
  129. package/dist/utils/attachAgent.js +20 -1
  130. package/dist/utils/attachAgent.js.map +1 -1
  131. package/dist/utils/codexHooks.d.ts +25 -0
  132. package/dist/utils/codexHooks.d.ts.map +1 -0
  133. package/dist/utils/codexHooks.js +131 -0
  134. package/dist/utils/codexHooks.js.map +1 -0
  135. package/dist/utils/conflictResolutionPane.d.ts.map +1 -1
  136. package/dist/utils/conflictResolutionPane.js +4 -0
  137. package/dist/utils/conflictResolutionPane.js.map +1 -1
  138. package/dist/utils/generated-agents-doc.d.ts +1 -1
  139. package/dist/utils/generated-agents-doc.js +1 -1
  140. package/dist/utils/githubPullRequest.d.ts +18 -0
  141. package/dist/utils/githubPullRequest.d.ts.map +1 -0
  142. package/dist/utils/githubPullRequest.js +196 -0
  143. package/dist/utils/githubPullRequest.js.map +1 -0
  144. package/dist/utils/input.d.ts +7 -6
  145. package/dist/utils/input.d.ts.map +1 -1
  146. package/dist/utils/input.js +47 -24
  147. package/dist/utils/input.js.map +1 -1
  148. package/dist/utils/paneColors.d.ts +7 -0
  149. package/dist/utils/paneColors.d.ts.map +1 -0
  150. package/dist/utils/paneColors.js +42 -0
  151. package/dist/utils/paneColors.js.map +1 -0
  152. package/dist/utils/paneCreation.d.ts.map +1 -1
  153. package/dist/utils/paneCreation.js +103 -34
  154. package/dist/utils/paneCreation.js.map +1 -1
  155. package/dist/utils/paneTitle.d.ts +1 -0
  156. package/dist/utils/paneTitle.d.ts.map +1 -1
  157. package/dist/utils/paneTitle.js +5 -1
  158. package/dist/utils/paneTitle.js.map +1 -1
  159. package/dist/utils/paneTitlePrefix.d.ts +8 -0
  160. package/dist/utils/paneTitlePrefix.d.ts.map +1 -0
  161. package/dist/utils/paneTitlePrefix.js +20 -0
  162. package/dist/utils/paneTitlePrefix.js.map +1 -0
  163. package/dist/utils/popup.d.ts +1 -0
  164. package/dist/utils/popup.d.ts.map +1 -1
  165. package/dist/utils/popup.js +9 -2
  166. package/dist/utils/popup.js.map +1 -1
  167. package/dist/utils/prSummary.d.ts +34 -0
  168. package/dist/utils/prSummary.d.ts.map +1 -0
  169. package/dist/utils/prSummary.js +130 -0
  170. package/dist/utils/prSummary.js.map +1 -0
  171. package/dist/utils/projectActions.d.ts +6 -0
  172. package/dist/utils/projectActions.d.ts.map +1 -1
  173. package/dist/utils/projectActions.js +46 -0
  174. package/dist/utils/projectActions.js.map +1 -1
  175. package/dist/utils/reopenWorktree.d.ts.map +1 -1
  176. package/dist/utils/reopenWorktree.js +37 -5
  177. package/dist/utils/reopenWorktree.js.map +1 -1
  178. package/dist/utils/resumeBranches.d.ts.map +1 -1
  179. package/dist/utils/resumeBranches.js +9 -3
  180. package/dist/utils/resumeBranches.js.map +1 -1
  181. package/dist/utils/settingsManager.d.ts +11 -3
  182. package/dist/utils/settingsManager.d.ts.map +1 -1
  183. package/dist/utils/settingsManager.js +96 -21
  184. package/dist/utils/settingsManager.js.map +1 -1
  185. package/dist/utils/sidebarProjects.d.ts +10 -3
  186. package/dist/utils/sidebarProjects.d.ts.map +1 -1
  187. package/dist/utils/sidebarProjects.js +125 -10
  188. package/dist/utils/sidebarProjects.js.map +1 -1
  189. package/dist/utils/tmuxHookCommands.d.ts +6 -0
  190. package/dist/utils/tmuxHookCommands.d.ts.map +1 -1
  191. package/dist/utils/tmuxHookCommands.js +12 -0
  192. package/dist/utils/tmuxHookCommands.js.map +1 -1
  193. package/dist/utils/tmuxSendKeys.d.ts +2 -0
  194. package/dist/utils/tmuxSendKeys.d.ts.map +1 -0
  195. package/dist/utils/tmuxSendKeys.js +8 -0
  196. package/dist/utils/tmuxSendKeys.js.map +1 -0
  197. package/dist/utils/toastLayout.d.ts +10 -0
  198. package/dist/utils/toastLayout.d.ts.map +1 -0
  199. package/dist/utils/toastLayout.js +24 -0
  200. package/dist/utils/toastLayout.js.map +1 -0
  201. package/dist/utils/welcomePane.d.ts +5 -1
  202. package/dist/utils/welcomePane.d.ts.map +1 -1
  203. package/dist/utils/welcomePane.js +33 -3
  204. package/dist/utils/welcomePane.js.map +1 -1
  205. package/dist/utils/welcomePaneManager.d.ts +3 -1
  206. package/dist/utils/welcomePaneManager.d.ts.map +1 -1
  207. package/dist/utils/welcomePaneManager.js +40 -2
  208. package/dist/utils/welcomePaneManager.js.map +1 -1
  209. package/dist/workers/PaneWorker.js +77 -0
  210. package/dist/workers/PaneWorker.js.map +1 -1
  211. package/dist/workers/WorkerMessages.d.ts +9 -1
  212. package/dist/workers/WorkerMessages.d.ts.map +1 -1
  213. package/dist/workers/WorkerMessages.js.map +1 -1
  214. package/dist/workers/panePollingWorker.js +33 -2
  215. package/dist/workers/panePollingWorker.js.map +1 -1
  216. package/package.json +1 -1
package/README.ja.md ADDED
@@ -0,0 +1,93 @@
1
+ <p align="center">
2
+ <img src="./dmux.png" alt="dmux logo" width="400" />
3
+ </p>
4
+
5
+ <h3 align="center">tmuxとworktreeを使った並列エージェント</h3>
6
+
7
+ <p align="center">
8
+ 分離されたgit worktreeで複数のAIコーディングエージェントを管理。<br/>
9
+ ブランチ作成、開発、マージを &mdash; すべて並列で実行。
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://dmux.ai"><strong>ドキュメント</strong></a> &nbsp;&middot;&nbsp;
14
+ <a href="https://dmux.ai#getting-started"><strong>クイックスタート</strong></a> &nbsp;&middot;&nbsp;
15
+ <a href="https://github.com/formkit/dmux/issues"><strong>イシュー</strong></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <strong>言語:</strong>
20
+ <a href="./README.md">English</a> |
21
+ <a href="./README.ja.md">日本語</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ <img src="./dmux.webp" alt="dmux demo" width="100%" />
27
+
28
+ ## インストール
29
+
30
+ ```bash
31
+ npm install -g dmux
32
+ ```
33
+
34
+ ## クイックスタート
35
+
36
+ ```bash
37
+ cd /path/to/your/project
38
+ dmux
39
+ ```
40
+
41
+ `n`キーを押して新しいペインを作成し、プロンプトを入力、1つ以上のエージェントを選択(またはプレーンターミナルの場合は選択なし)すると、dmuxが残りの処理(worktree、ブランチ、エージェント起動)を自動的に処理します。
42
+
43
+ ## dmuxとは
44
+
45
+ dmuxは各タスクに対してtmuxペインを作成します。各ペインには独自のgit worktreeとブランチが割り当てられるため、エージェントは完全に分離されて作業できます。タスクが完了したら、ペインメニューで`m`を押してマージを選択すると、メインブランチに変更を取り込めます。
46
+
47
+ - **Worktree分離** &mdash; 各ペインは完全な作業コピーで、エージェント間の競合はありません
48
+ - **エージェントサポート** &mdash; Claude Code、Codex、OpenCode、Cline CLI、Gemini CLI、Qwen CLI、Amp CLI、pi CLI、Cursor CLI、Copilot CLI、Crush CLI
49
+ - **複数選択起動** &mdash; プロンプトごとに有効なエージェントを任意の組み合わせで選択可能
50
+ - **AI命名** &mdash; ブランチとコミットメッセージを自動生成
51
+ - **スマートマージ** &mdash; 自動コミット、マージ、クリーンアップを1ステップで実行
52
+ - **macOS通知** &mdash; バックグラウンドペインが処理完了時にネイティブのアラートを送信
53
+ - **内蔵ファイルブラウザ** &mdash; dmuxを離れずにペインのworktreeを閲覧、ファイル検索、コードや差分のプレビュー
54
+ - **ペイン表示制御** &mdash; 個別ペインの非表示、プロジェクトの分離、後から全表示の復元
55
+ - **マルチプロジェクト** &mdash; 同じセッションに複数のリポジトリを追加
56
+ - **ライフサイクルフック** &mdash; worktree作成、プレマージ、ポストマージ時のスクリプト実行
57
+
58
+ ## キーボードショートカット
59
+
60
+ | キー | アクション |
61
+ |-----|--------|
62
+ | `n` | 新しいペイン(worktree + エージェント) |
63
+ | `t` | 新しいターミナルペイン |
64
+ | `j` / `Enter` | ペインにジャンプ |
65
+ | `m` | ペインメニューを開く |
66
+ | `f` | 選択したペインのworktreeを閲覧 |
67
+ | `x` | ペインを閉じる |
68
+ | `h` | 選択したペインを表示/非表示 |
69
+ | `H` | 他のすべてのペインを表示/非表示 |
70
+ | `p` | 別のプロジェクトに新しいペインを作成 |
71
+ | `P` | 選択したプロジェクトのペインのみ表示、その後すべて表示 |
72
+ | `s` | 設定 |
73
+ | `q` | 終了 |
74
+
75
+ ## 必要要件
76
+
77
+ - tmux 3.0+
78
+ - Node.js 18+
79
+ - Git 2.20+
80
+ - 少なくとも1つのサポート対象エージェントCLI(例:[Claude Code](https://docs.anthropic.com/en/docs/claude-code)、[Codex](https://github.com/openai/codex)、[OpenCode](https://github.com/opencode-ai/opencode)、[Cline CLI](https://docs.cline.bot/cline-cli/getting-started)、[Gemini CLI](https://github.com/google-gemini/gemini-cli)、[Qwen CLI](https://github.com/QwenLM/qwen-code)、[Amp CLI](https://ampcode.com/manual)、[pi CLI](https://www.npmjs.com/package/@mariozechner/pi-coding-agent)、[Cursor CLI](https://docs.cursor.com/en/cli/overview)、[Copilot CLI](https://github.com/github/copilot-cli)、[Crush CLI](https://github.com/charmbracelet/crush))
81
+ - [OpenRouter APIキー](https://openrouter.ai/)(オプション、AIブランチ名とコミットメッセージ用)
82
+
83
+ ## ドキュメント
84
+
85
+ 完全なドキュメントは **[dmux.ai](https://dmux.ai)** でご覧いただけます。セットアップガイド、設定、フックの情報が含まれています。
86
+
87
+ ## コントリビュート
88
+
89
+ 推奨されるローカル「dmux-on-dmux」開発ループ、フックセットアップ、PRワークフローについては、**[CONTRIBUTING.md](./CONTRIBUTING.md)** をご覧ください。
90
+
91
+ ## ライセンス
92
+
93
+ MIT
package/README.md CHANGED
@@ -15,6 +15,12 @@
15
15
  <a href="https://github.com/formkit/dmux/issues"><strong>Issues</strong></a>
16
16
  </p>
17
17
 
18
+ <p align="center">
19
+ <strong>Language:</strong>
20
+ <a href="./README.md">English</a> |
21
+ <a href="./README.ja.md">日本語</a>
22
+ </p>
23
+
18
24
  ---
19
25
 
20
26
  <img src="./dmux.webp" alt="dmux demo" width="100%" />
@@ -36,7 +42,7 @@ Press `n` to create a new pane, type a prompt, pick one or more agents (or none
36
42
 
37
43
  ## What it does
38
44
 
39
- dmux creates a tmux pane for each task. Every pane gets its own git worktree and branch so agents work in complete isolation. When a task is done, open the pane menu with `m` and choose Merge to bring it back into your main branch.
45
+ dmux creates a tmux pane for each task. Every pane gets its own git worktree and branch so agents work in complete isolation. When a task is done, open the pane menu with `m` and choose Merge to bring it back into your main branch, or Create GitHub PR to push the branch and file a pull request.
40
46
 
41
47
  - **Worktree isolation** &mdash; each pane is a full working copy, no conflicts between agents
42
48
  - **Agent support** &mdash; Claude Code, Codex, OpenCode, Cline CLI, Gemini CLI, Qwen CLI, Amp CLI, pi CLI, Cursor CLI, Copilot CLI, and Crush CLI
@@ -1 +1 @@
1
- {"version":3,"file":"DmuxApp.d.ts","sourceRoot":"","sources":["../src/DmuxApp.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAA;AAsE3D,OAAO,KAAK,EAEV,YAAY,EAEb,MAAM,YAAY,CAAA;AAenB,QAAA,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAmvCnC,CAAA;AAED,eAAe,OAAO,CAAA"}
1
+ {"version":3,"file":"DmuxApp.d.ts","sourceRoot":"","sources":["../src/DmuxApp.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAA;AAwEnE,OAAO,KAAK,EAEV,YAAY,EAGb,MAAM,YAAY,CAAA;AAmCnB,QAAA,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CA2jDnC,CAAA;AAED,eAAe,OAAO,CAAA"}
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
  }
@@ -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,7 +1103,7 @@ 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,
878
1109
  projectActionItems: projectActionLayout.actionItems,
@@ -884,7 +1115,8 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
884
1115
  // - Normal mode calculation:
885
1116
  // - Base footer: 4 lines (marginTop + logs divider + logs line + keyboard shortcuts)
886
1117
  // - Footer tip: +1 line when footer tips are enabled
887
- // - Toast: +2 lines (toast message + marginBottom) if currentToast exists
1118
+ // - Toast (active): wrapped lines + header + marginBottom
1119
+ // - Toast (queued, transitioning): header + marginBottom (2 lines)
888
1120
  // - Debug info: +1 line if DEBUG_DMUX
889
1121
  // - Status line: +1 line if updateAvailable/currentBranch/debugMessage
890
1122
  // - Status messages: +1 line per active message
@@ -896,20 +1128,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
896
1128
  else {
897
1129
  footerLines = 0;
898
1130
  if (showFooterHelp) {
899
- footerLines = 4; // marginTop + logs divider + logs + shortcuts
1131
+ footerLines = 3; // logs divider + logs + shortcuts
900
1132
  if (currentFooterTip) {
901
1133
  footerLines += 1;
902
1134
  }
903
1135
  // Add toast notification (calculate wrapped lines + header)
904
1136
  if (currentToast) {
905
1137
  // Toast format: "✓ message" - icon (1) + space (1) + message
906
- const iconAndSpaceLength = 2;
907
- 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);
908
1141
  // Available width is sidebar width (40) minus padding/margins (~2)
909
1142
  const availableWidth = SIDEBAR_WIDTH - 2;
910
- const wrappedLines = Math.ceil(toastTextLength / availableWidth);
1143
+ const wrappedLines = Math.ceil(toastDisplayWidth / availableWidth);
911
1144
  footerLines += wrappedLines + 1 + 1; // wrapped lines + header line + marginBottom
912
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
+ }
913
1151
  // Add debug info
914
1152
  if (process.env.DEBUG_DMUX) {
915
1153
  footerLines += 1;
@@ -928,9 +1166,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
928
1166
  }
929
1167
  }
930
1168
  const contentHeight = Math.max(terminalHeight - footerLines, 10);
931
- return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
1169
+ return (React.createElement(Box, { key: `theme-${selectedThemeName}-${themeRefreshNonce}`, flexDirection: "column", height: terminalHeight },
932
1170
  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 }),
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 }),
934
1172
  showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
935
1173
  showFileCopyPrompt && React.createElement(FileCopyPrompt, null),
936
1174
  showHooksPrompt && (React.createElement(TmuxHooksPromptDialog, { selectedIndex: hooksPromptIndex }))),