@alivault/pico 0.11.0 → 0.13.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 (311) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/addon-fit-CMMXLNsg.js +1 -0
  3. package/.output/public/assets/{index-DPU34F0T.js → index-CW1T9dk-.js} +11 -10
  4. package/.output/public/assets/routes-2LboeUx4.js +2194 -0
  5. package/.output/public/assets/styles-B1ITYh-r.css +2 -0
  6. package/.output/public/assets/xterm-CCMwI7sy.js +36 -0
  7. package/.output/server/_libs/lucide-react.mjs +46 -1
  8. package/.output/server/_libs/xterm__addon-fit.mjs +44 -0
  9. package/.output/server/_libs/xterm__xterm.mjs +10234 -0
  10. package/.output/server/_ssr/{router-Ccz1kegF.mjs → router-l__gf5Y7.mjs} +866 -173
  11. package/.output/server/_ssr/{routes-D3Hu5M6D.mjs → routes-CuWg5WTI.mjs} +167 -27
  12. package/.output/server/_ssr/{routes-DGf2MDLT.mjs → routes-DySm_W8N.mjs} +1946 -631
  13. package/.output/server/_ssr/ssr.mjs +2 -2
  14. package/.output/server/{_tanstack-start-manifest_v-BWLjOj7J.mjs → _tanstack-start-manifest_v-CVoKKKc_.mjs} +5 -4
  15. package/.output/server/index.mjs +542 -528
  16. package/.output/server/node_modules/node-pty/LICENSE +69 -0
  17. package/.output/server/node_modules/node-pty/README.md +165 -0
  18. package/.output/server/node_modules/node-pty/binding.gyp +111 -0
  19. package/.output/server/node_modules/node-pty/build/Makefile +369 -0
  20. package/.output/server/node_modules/node-pty/build/Release/pty.node +0 -0
  21. package/.output/server/node_modules/node-pty/build/binding.Makefile +6 -0
  22. package/.output/server/node_modules/node-pty/build/pty.target.mk +168 -0
  23. package/.output/server/node_modules/node-pty/deps/.editorconfig +2 -0
  24. package/.output/server/node_modules/node-pty/deps/winpty/.drone.yml +17 -0
  25. package/.output/server/node_modules/node-pty/deps/winpty/.gitattributes +19 -0
  26. package/.output/server/node_modules/node-pty/deps/winpty/LICENSE +21 -0
  27. package/.output/server/node_modules/node-pty/deps/winpty/Makefile +166 -0
  28. package/.output/server/node_modules/node-pty/deps/winpty/README.md +151 -0
  29. package/.output/server/node_modules/node-pty/deps/winpty/RELEASES.md +280 -0
  30. package/.output/server/node_modules/node-pty/deps/winpty/VERSION.txt +1 -0
  31. package/.output/server/node_modules/node-pty/deps/winpty/configure +167 -0
  32. package/.output/server/node_modules/node-pty/deps/winpty/misc/BufferResizeTests.cc +90 -0
  33. package/.output/server/node_modules/node-pty/deps/winpty/misc/ChangeScreenBuffer.cc +53 -0
  34. package/.output/server/node_modules/node-pty/deps/winpty/misc/ClearConsole.cc +72 -0
  35. package/.output/server/node_modules/node-pty/deps/winpty/misc/ConinMode.cc +117 -0
  36. package/.output/server/node_modules/node-pty/deps/winpty/misc/ConinMode.ps1 +116 -0
  37. package/.output/server/node_modules/node-pty/deps/winpty/misc/ConoutMode.cc +113 -0
  38. package/.output/server/node_modules/node-pty/deps/winpty/misc/DebugClient.py +42 -0
  39. package/.output/server/node_modules/node-pty/deps/winpty/misc/DebugServer.py +63 -0
  40. package/.output/server/node_modules/node-pty/deps/winpty/misc/DumpLines.py +5 -0
  41. package/.output/server/node_modules/node-pty/deps/winpty/misc/EnableExtendedFlags.txt +46 -0
  42. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP437-Consolas.txt +528 -0
  43. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP437-Lucida.txt +633 -0
  44. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP932.txt +630 -0
  45. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP936.txt +630 -0
  46. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP949.txt +630 -0
  47. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP950.txt +630 -0
  48. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt +16 -0
  49. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/Results.txt +4 -0
  50. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt +144 -0
  51. package/.output/server/node_modules/node-pty/deps/winpty/misc/FontSurvey.cc +100 -0
  52. package/.output/server/node_modules/node-pty/deps/winpty/misc/FormatChar.h +21 -0
  53. package/.output/server/node_modules/node-pty/deps/winpty/misc/FreezePerfTest.cc +62 -0
  54. package/.output/server/node_modules/node-pty/deps/winpty/misc/GetCh.cc +20 -0
  55. package/.output/server/node_modules/node-pty/deps/winpty/misc/GetConsolePos.cc +41 -0
  56. package/.output/server/node_modules/node-pty/deps/winpty/misc/GetFont.cc +261 -0
  57. package/.output/server/node_modules/node-pty/deps/winpty/misc/IdentifyConsoleWindow.ps1 +51 -0
  58. package/.output/server/node_modules/node-pty/deps/winpty/misc/IsNewConsole.cc +87 -0
  59. package/.output/server/node_modules/node-pty/deps/winpty/misc/MouseInputNotes.txt +90 -0
  60. package/.output/server/node_modules/node-pty/deps/winpty/misc/MoveConsoleWindow.cc +34 -0
  61. package/.output/server/node_modules/node-pty/deps/winpty/misc/Notes.txt +219 -0
  62. package/.output/server/node_modules/node-pty/deps/winpty/misc/OSVersion.cc +27 -0
  63. package/.output/server/node_modules/node-pty/deps/winpty/misc/ScreenBufferFreezeInactive.cc +101 -0
  64. package/.output/server/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest.cc +671 -0
  65. package/.output/server/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest2.cc +151 -0
  66. package/.output/server/node_modules/node-pty/deps/winpty/misc/SelectAllTest.cc +45 -0
  67. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetBufferSize.cc +32 -0
  68. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetCursorPos.cc +10 -0
  69. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetFont.cc +145 -0
  70. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetWindowRect.cc +36 -0
  71. package/.output/server/node_modules/node-pty/deps/winpty/misc/ShowArgv.cc +12 -0
  72. package/.output/server/node_modules/node-pty/deps/winpty/misc/ShowConsoleInput.cc +40 -0
  73. package/.output/server/node_modules/node-pty/deps/winpty/misc/Spew.py +5 -0
  74. package/.output/server/node_modules/node-pty/deps/winpty/misc/TestUtil.cc +172 -0
  75. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnicodeDoubleWidthTest.cc +102 -0
  76. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnicodeWideTest1.cc +246 -0
  77. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnicodeWideTest2.cc +130 -0
  78. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnixEcho.cc +89 -0
  79. package/.output/server/node_modules/node-pty/deps/winpty/misc/Utf16Echo.cc +46 -0
  80. package/.output/server/node_modules/node-pty/deps/winpty/misc/VeryLargeRead.cc +122 -0
  81. package/.output/server/node_modules/node-pty/deps/winpty/misc/VkEscapeTest.cc +56 -0
  82. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win10ResizeWhileFrozen.cc +52 -0
  83. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win10WrapTest1.cc +57 -0
  84. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win10WrapTest2.cc +30 -0
  85. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Echo1.cc +26 -0
  86. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Echo2.cc +19 -0
  87. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Test1.cc +46 -0
  88. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Test2.cc +70 -0
  89. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Test3.cc +78 -0
  90. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Write1.cc +44 -0
  91. package/.output/server/node_modules/node-pty/deps/winpty/misc/WindowsBugCrashReader.cc +27 -0
  92. package/.output/server/node_modules/node-pty/deps/winpty/misc/WriteConsole.cc +106 -0
  93. package/.output/server/node_modules/node-pty/deps/winpty/misc/build32.sh +9 -0
  94. package/.output/server/node_modules/node-pty/deps/winpty/misc/build64.sh +9 -0
  95. package/.output/server/node_modules/node-pty/deps/winpty/misc/color-test.sh +212 -0
  96. package/.output/server/node_modules/node-pty/deps/winpty/misc/font-notes.txt +300 -0
  97. package/.output/server/node_modules/node-pty/deps/winpty/misc/winbug-15048.cc +201 -0
  98. package/.output/server/node_modules/node-pty/deps/winpty/ship/build-pty4j-libpty.bat +36 -0
  99. package/.output/server/node_modules/node-pty/deps/winpty/ship/common_ship.py +53 -0
  100. package/.output/server/node_modules/node-pty/deps/winpty/ship/make_msvc_package.py +165 -0
  101. package/.output/server/node_modules/node-pty/deps/winpty/ship/ship.py +108 -0
  102. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Agent.cc +613 -0
  103. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Agent.h +103 -0
  104. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/AgentCreateDesktop.cc +84 -0
  105. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/AgentCreateDesktop.h +28 -0
  106. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.cc +632 -0
  107. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.h +28 -0
  108. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.cc +852 -0
  109. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.h +109 -0
  110. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInputReencoding.cc +121 -0
  111. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInputReencoding.h +36 -0
  112. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleLine.cc +152 -0
  113. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleLine.h +41 -0
  114. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Coord.h +87 -0
  115. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DebugShowInput.cc +239 -0
  116. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DebugShowInput.h +32 -0
  117. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DefaultInputMap.cc +422 -0
  118. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DefaultInputMap.h +28 -0
  119. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DsrSender.h +30 -0
  120. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/EventLoop.cc +99 -0
  121. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/EventLoop.h +47 -0
  122. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/InputMap.cc +246 -0
  123. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/InputMap.h +114 -0
  124. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/LargeConsoleRead.cc +71 -0
  125. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/LargeConsoleRead.h +68 -0
  126. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/NamedPipe.cc +378 -0
  127. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/NamedPipe.h +125 -0
  128. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Scraper.cc +699 -0
  129. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Scraper.h +103 -0
  130. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/SimplePool.h +75 -0
  131. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/SmallRect.h +143 -0
  132. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Terminal.cc +535 -0
  133. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Terminal.h +69 -0
  134. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/UnicodeEncoding.h +157 -0
  135. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/UnicodeEncodingTest.cc +189 -0
  136. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32Console.cc +107 -0
  137. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32Console.h +67 -0
  138. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32ConsoleBuffer.cc +193 -0
  139. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32ConsoleBuffer.h +99 -0
  140. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/main.cc +114 -0
  141. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/subdir.mk +61 -0
  142. package/.output/server/node_modules/node-pty/deps/winpty/src/configurations.gypi +60 -0
  143. package/.output/server/node_modules/node-pty/deps/winpty/src/debugserver/DebugServer.cc +117 -0
  144. package/.output/server/node_modules/node-pty/deps/winpty/src/debugserver/subdir.mk +41 -0
  145. package/.output/server/node_modules/node-pty/deps/winpty/src/include/winpty.h +242 -0
  146. package/.output/server/node_modules/node-pty/deps/winpty/src/include/winpty_constants.h +131 -0
  147. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/AgentLocation.cc +75 -0
  148. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/AgentLocation.h +28 -0
  149. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/LibWinptyException.h +54 -0
  150. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/WinptyInternal.h +72 -0
  151. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/subdir.mk +46 -0
  152. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/winpty.cc +970 -0
  153. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/AgentMsg.h +38 -0
  154. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/BackgroundDesktop.cc +122 -0
  155. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/BackgroundDesktop.h +73 -0
  156. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/Buffer.cc +103 -0
  157. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/Buffer.h +102 -0
  158. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/DebugClient.cc +187 -0
  159. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/DebugClient.h +38 -0
  160. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/GenRandom.cc +138 -0
  161. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/GenRandom.h +55 -0
  162. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/GetCommitHash.bat +13 -0
  163. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/Mutex.h +54 -0
  164. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/OsModule.h +63 -0
  165. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/OwnedHandle.cc +36 -0
  166. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/OwnedHandle.h +45 -0
  167. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/PrecompiledHeader.h +43 -0
  168. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringBuilder.h +227 -0
  169. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringBuilderTest.cc +114 -0
  170. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringUtil.cc +55 -0
  171. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringUtil.h +80 -0
  172. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/TimeMeasurement.h +63 -0
  173. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/UnixCtrlChars.h +45 -0
  174. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/UpdateGenVersion.bat +20 -0
  175. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsSecurity.cc +460 -0
  176. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsSecurity.h +104 -0
  177. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsVersion.cc +252 -0
  178. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsVersion.h +29 -0
  179. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyAssert.cc +55 -0
  180. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyAssert.h +64 -0
  181. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyException.cc +57 -0
  182. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyException.h +43 -0
  183. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyVersion.cc +42 -0
  184. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyVersion.h +27 -0
  185. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/winpty_snprintf.h +99 -0
  186. package/.output/server/node_modules/node-pty/deps/winpty/src/subdir.mk +5 -0
  187. package/.output/server/node_modules/node-pty/deps/winpty/src/tests/subdir.mk +28 -0
  188. package/.output/server/node_modules/node-pty/deps/winpty/src/tests/trivial_test.cc +158 -0
  189. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/InputHandler.cc +114 -0
  190. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/InputHandler.h +56 -0
  191. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/OutputHandler.cc +80 -0
  192. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/OutputHandler.h +53 -0
  193. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/Util.cc +86 -0
  194. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/Util.h +31 -0
  195. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/WakeupFd.cc +70 -0
  196. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/WakeupFd.h +42 -0
  197. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/main.cc +729 -0
  198. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/subdir.mk +41 -0
  199. package/.output/server/node_modules/node-pty/deps/winpty/src/winpty.gyp +234 -0
  200. package/.output/server/node_modules/node-pty/deps/winpty/vcbuild.bat +83 -0
  201. package/.output/server/node_modules/node-pty/lib/conpty_console_list_agent.js +16 -0
  202. package/.output/server/node_modules/node-pty/lib/conpty_console_list_agent.js.map +1 -0
  203. package/.output/server/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  204. package/.output/server/node_modules/node-pty/lib/eventEmitter2.js.map +1 -0
  205. package/.output/server/node_modules/node-pty/lib/eventEmitter2.test.js +30 -0
  206. package/.output/server/node_modules/node-pty/lib/eventEmitter2.test.js.map +1 -0
  207. package/.output/server/node_modules/node-pty/lib/index.js +52 -0
  208. package/.output/server/node_modules/node-pty/lib/index.js.map +1 -0
  209. package/.output/server/node_modules/node-pty/lib/interfaces.js +7 -0
  210. package/.output/server/node_modules/node-pty/lib/interfaces.js.map +1 -0
  211. package/.output/server/node_modules/node-pty/lib/shared/conout.js +11 -0
  212. package/.output/server/node_modules/node-pty/lib/shared/conout.js.map +1 -0
  213. package/.output/server/node_modules/node-pty/lib/terminal.js +190 -0
  214. package/.output/server/node_modules/node-pty/lib/terminal.js.map +1 -0
  215. package/.output/server/node_modules/node-pty/lib/terminal.test.js +139 -0
  216. package/.output/server/node_modules/node-pty/lib/terminal.test.js.map +1 -0
  217. package/.output/server/node_modules/node-pty/lib/testUtils.test.js +28 -0
  218. package/.output/server/node_modules/node-pty/lib/testUtils.test.js.map +1 -0
  219. package/.output/server/node_modules/node-pty/lib/types.js +7 -0
  220. package/.output/server/node_modules/node-pty/lib/types.js.map +1 -0
  221. package/.output/server/node_modules/node-pty/lib/unixTerminal.js +346 -0
  222. package/.output/server/node_modules/node-pty/lib/unixTerminal.js.map +1 -0
  223. package/.output/server/node_modules/node-pty/lib/unixTerminal.test.js +351 -0
  224. package/.output/server/node_modules/node-pty/lib/unixTerminal.test.js.map +1 -0
  225. package/.output/server/node_modules/node-pty/lib/utils.js +39 -0
  226. package/.output/server/node_modules/node-pty/lib/utils.js.map +1 -0
  227. package/.output/server/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  228. package/.output/server/node_modules/node-pty/lib/windowsConoutConnection.js.map +1 -0
  229. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.js +320 -0
  230. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.js.map +1 -0
  231. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.test.js +90 -0
  232. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.test.js.map +1 -0
  233. package/.output/server/node_modules/node-pty/lib/windowsTerminal.js +199 -0
  234. package/.output/server/node_modules/node-pty/lib/windowsTerminal.js.map +1 -0
  235. package/.output/server/node_modules/node-pty/lib/windowsTerminal.test.js +219 -0
  236. package/.output/server/node_modules/node-pty/lib/windowsTerminal.test.js.map +1 -0
  237. package/.output/server/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  238. package/.output/server/node_modules/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
  239. package/.output/server/node_modules/node-pty/package.json +64 -0
  240. package/.output/server/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  241. package/.output/server/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  242. package/.output/server/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  243. package/.output/server/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  244. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  245. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  246. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  247. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  248. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  249. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  250. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/pty.node +0 -0
  251. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/pty.pdb +0 -0
  252. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
  253. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty-agent.pdb +0 -0
  254. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
  255. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty.pdb +0 -0
  256. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  257. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  258. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  259. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  260. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  261. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  262. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/pty.node +0 -0
  263. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/pty.pdb +0 -0
  264. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
  265. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty-agent.pdb +0 -0
  266. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
  267. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty.pdb +0 -0
  268. package/.output/server/node_modules/node-pty/scripts/gen-compile-commands.js +8 -0
  269. package/.output/server/node_modules/node-pty/scripts/increment-version.js +54 -0
  270. package/.output/server/node_modules/node-pty/scripts/post-install.js +80 -0
  271. package/.output/server/node_modules/node-pty/scripts/prebuild.js +34 -0
  272. package/.output/server/node_modules/node-pty/src/conpty_console_list_agent.ts +15 -0
  273. package/.output/server/node_modules/node-pty/src/eventEmitter2.test.ts +30 -0
  274. package/.output/server/node_modules/node-pty/src/eventEmitter2.ts +48 -0
  275. package/.output/server/node_modules/node-pty/src/index.ts +52 -0
  276. package/.output/server/node_modules/node-pty/src/interfaces.ts +130 -0
  277. package/.output/server/node_modules/node-pty/src/native.d.ts +54 -0
  278. package/.output/server/node_modules/node-pty/src/shared/conout.ts +15 -0
  279. package/.output/server/node_modules/node-pty/src/terminal.test.ts +119 -0
  280. package/.output/server/node_modules/node-pty/src/terminal.ts +211 -0
  281. package/.output/server/node_modules/node-pty/src/testUtils.test.ts +23 -0
  282. package/.output/server/node_modules/node-pty/src/tsconfig.json +22 -0
  283. package/.output/server/node_modules/node-pty/src/types.ts +15 -0
  284. package/.output/server/node_modules/node-pty/src/unix/pty.cc +799 -0
  285. package/.output/server/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  286. package/.output/server/node_modules/node-pty/src/unixTerminal.test.ts +367 -0
  287. package/.output/server/node_modules/node-pty/src/unixTerminal.ts +388 -0
  288. package/.output/server/node_modules/node-pty/src/utils.ts +29 -0
  289. package/.output/server/node_modules/node-pty/src/win/conpty.cc +583 -0
  290. package/.output/server/node_modules/node-pty/src/win/conpty.h +41 -0
  291. package/.output/server/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  292. package/.output/server/node_modules/node-pty/src/win/path_util.cc +95 -0
  293. package/.output/server/node_modules/node-pty/src/win/path_util.h +26 -0
  294. package/.output/server/node_modules/node-pty/src/win/winpty.cc +333 -0
  295. package/.output/server/node_modules/node-pty/src/windowsConoutConnection.ts +82 -0
  296. package/.output/server/node_modules/node-pty/src/windowsPtyAgent.test.ts +94 -0
  297. package/.output/server/node_modules/node-pty/src/windowsPtyAgent.ts +321 -0
  298. package/.output/server/node_modules/node-pty/src/windowsTerminal.test.ts +229 -0
  299. package/.output/server/node_modules/node-pty/src/windowsTerminal.ts +203 -0
  300. package/.output/server/node_modules/node-pty/src/worker/conoutSocketWorker.ts +22 -0
  301. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  302. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  303. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  304. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  305. package/.output/server/node_modules/node-pty/typings/node-pty.d.ts +211 -0
  306. package/.output/server/package.json +1 -0
  307. package/package.json +11 -4
  308. package/.output/public/assets/routes-CVlcrX-A.js +0 -2195
  309. package/.output/public/assets/styles-Chvsnn7o.css +0 -2
  310. package/scripts/patch-react-jsx-dev-runtime.mjs +0 -93
  311. /package/.output/public/assets/{wasm-CEUrWlVz.js → wasm-D_MQmneJ.js} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { i as __toESM } from "../_runtime.mjs";
2
2
  import { $ as require_react, A as TabsTab, B as Input, C as MenuPositioner, D as MenuGroupLabel, E as MenuItem, F as DialogPopup, H as useRender, I as DialogDescription$1, L as DialogClose$1, M as DialogTitle$1, N as DialogRoot, O as MenuGroup, P as DialogPortal$1, Q as require_jsx_runtime, R as DialogBackdrop, S as MenuRoot, T as MenuPopup, V as Button, Y as mergeProps, _ as AccordionItem$1, a as ContextMenuRoot, b as MenuTrigger, c as CollapsibleRoot, d as PopoverPortal, f as PopoverTrigger$1, g as AccordionHeader, h as AccordionTrigger$1, i as ContextMenuTrigger$1, j as TabsRoot, k as TabsList$1, l as PopoverPopup, m as AccordionPanel, n as ToggleGroup, o as CollapsiblePanel, p as PopoverRoot, r as Toggle, s as CollapsibleTrigger$1, t as AlertDialogRoot, u as PopoverPositioner, v as AccordionRoot, w as MenuPortal, x as MenuSubmenuRoot, y as MenuSubmenuTrigger, z as Separator } from "../_libs/@base-ui/react+[...].mjs";
3
3
  import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
4
- import { A as ExternalLink, B as ChevronRight, C as ImagePlus, D as Folder, E as GitCommitHorizontal, F as Command, G as ArrowUpRight, H as Check, J as ArrowDown, L as ChevronsUpDown, M as EllipsisVertical, N as Download, O as FolderPlus, P as Copy, R as ChevronsDownUp, T as GitBranch, U as ArrowUp, V as ChevronDown, W as ArrowUpToLine, _ as OctagonX, a as TriangleAlert, b as ListStart, c as SquarePen, d as Plus, f as PanelRight, h as PanelLeftClose, i as Undo2, j as Ellipsis, k as FileDiff, l as Settings2, m as PanelLeftOpen, n as WandSparkles, o as Tag, p as PanelLeft, q as ArrowLeft, r as Upload, s as Square, t as X, u as Search, v as Minimize2, w as GitPullRequestArrow, x as ListEnd, y as Maximize2 } from "../_libs/lucide-react.mjs";
5
- import { $ as normalizeThemeFamily, A as Tooltip$1, C as SESSION_DONE_SOUND_ENABLED_STORAGE_KEY, Ct as themeFamilyDescription, D as THEME_FAMILIES, Dt as themeFamilySupportsColorMode, E as THEME_COLOR_MODE_STORAGE_KEY, Et as themeFamilyLabel, F as appliedThemeClass, H as createInitialSessionState, I as appliedThemeClassColorMode, K as flattenTree, N as TooltipTrigger, O as THEME_STORAGE_KEY, Ot as thinkingSummaryText, P as VIEWER_CONTEXT_STORAGE_KEY, Q as normalizeThemeColorMode, R as buildItemsFromSync, S as SESSION_DONE_DESKTOP_NOTIFICATIONS_ENABLED_STORAGE_KEY, St as themeColorModeLabel, T as Spinner, Tt as themeFamilyKeywords, V as createContextId, X as normalizeSessionSelectionKeys, Y as normalizePromptImage, Z as normalizeStoredDirectoryList, _ as RIGHT_SIDEBAR_HISTORY_HEIGHT_STORAGE_KEY, _t as rememberStoredPromptDraft, a as COLLAPSED_DIRECTORIES_STORAGE_KEY, at as readStoredCollapsedDirectories, b as RIGHT_SIDEBAR_WIDTHS_STORAGE_KEY, bt as sameContextUsage, c as Kbd, ct as readStoredPinnedSessionKeys, d as PICO_DIFF_SHIKI_THEMES, dt as readStoredRightSidebarOpen, et as previewUrlForImage, f as PICO_SHIKI_VARIABLE_DEFAULTS, ft as readStoredSessionDoneDesktopNotificationsEnabled, g as RIGHT_SIDEBAR_FILE_TREE_WIDTH_STORAGE_KEY, gt as readStoredThemeColorMode, h as RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, ht as readStoredTheme, i as CENTER_MESSAGES_STORAGE_KEY, it as readStoredCenterMessages, j as TooltipContent, k as TitleTooltip, l as KeyboardShortcutContent, lt as readStoredPromptDraft, m as RECENT_DIRECTORIES_STORAGE_KEY, mt as readStoredSidebarDirectories, n as APPLIED_THEME_STORAGE_KEY, nt as promptDraftKeyMatchesOwner, o as DRAFT_DIRECTORY_STORAGE_KEY, ot as readStoredDraftDirectory, p as PINNED_SESSIONS_STORAGE_KEY, pt as readStoredSessionDoneSoundEnabled, q as getSessionTitle, r as AUTO_SCROLL_ENABLED_STORAGE_KEY, rt as readStoredAutoScrollEnabled, s as HIDE_TOOL_BLOCKS_STORAGE_KEY, st as readStoredHideToolBlocks, tt as promptDraftKey, ut as readStoredRecentDirectories, v as RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY, vt as safeLocalStorageGetItem, w as SIDEBAR_DIRECTORIES_STORAGE_KEY, wt as themeFamilyFixedMode, x as Route, xt as sessionListEntryKey, y as RIGHT_SIDEBAR_OPEN_STORAGE_KEY, yt as safeLocalStorageSetItem, z as cn } from "./routes-D3Hu5M6D.mjs";
4
+ import { A as Folder, B as ChevronsUpDown, C as ListStart, D as GitPullRequestArrow, E as ImagePlus, F as EllipsisVertical, G as Check, I as Download, J as ArrowUpRight, K as ArrowUp, L as Copy, M as FileDiff, N as ExternalLink, O as GitBranch, P as Ellipsis, R as Command, S as Maximize2, U as ChevronRight, V as ChevronsDownUp, W as ChevronDown, X as ArrowLeft, Z as ArrowDown, _ as PanelLeftClose, a as TriangleAlert, b as Minus, c as Square, d as Settings2, f as Search, g as PanelLeftOpen, h as PanelLeft, i as Undo2, j as FolderPlus, k as GitCommitHorizontal, l as SquareTerminal, m as PanelRight, n as WandSparkles, o as Trash2, p as Plus, q as ArrowUpToLine, r as Upload, s as Tag, t as X, u as SquarePen, w as ListEnd, x as Minimize2, y as OctagonX } from "../_libs/lucide-react.mjs";
5
+ import { $ as normalizeSessionSelectionKeys, A as THEME_STORAGE_KEY, At as themeFamilyDescription, B as buildItemsFromSync, C as SESSION_DONE_SOUND_ENABLED_STORAGE_KEY, Ct as rememberStoredPromptDraft, D as TERMINAL_PANEL_OPEN_STORAGE_KEY, Dt as sameContextUsage, E as TERMINAL_PANEL_HEIGHT_STORAGE_KEY, Et as safeLocalStorageSetItem, F as TooltipTrigger, Ft as thinkingSummaryText, G as exploreShellCommandNameFromTool, I as VIEWER_CONTEXT_STORAGE_KEY, It as toolArgsAreIncompleteJsonObject, L as appliedThemeClass, Lt as toolCategoryFromTool, M as Tooltip$1, Mt as themeFamilyKeywords, N as TooltipContent, Nt as themeFamilyLabel, O as THEME_COLOR_MODE_STORAGE_KEY, Ot as sessionListEntryKey, Pt as themeFamilySupportsColorMode, Q as normalizePromptImage, R as appliedThemeClassColorMode, Rt as toolCouldBecomeExploreFromTool, S as SESSION_DONE_DESKTOP_NOTIFICATIONS_ENABLED_STORAGE_KEY, St as readStoredThemeColorMode, T as Spinner, Tt as safeLocalStorageGetItem, U as createContextId, V as cn, W as createInitialSessionState, X as getSessionTitle, Y as flattenTree, _ as RIGHT_SIDEBAR_HISTORY_HEIGHT_STORAGE_KEY, _t as readStoredSessionDoneSoundEnabled, a as COLLAPSED_DIRECTORIES_STORAGE_KEY, at as promptDraftKeyMatchesOwner, b as RIGHT_SIDEBAR_WIDTHS_STORAGE_KEY, bt as readStoredTerminalTabs, c as Kbd, ct as readStoredCenterMessages, d as PICO_DIFF_SHIKI_THEMES, dt as readStoredHideToolBlocks, et as normalizeStoredDirectoryList, f as PICO_SHIKI_VARIABLE_DEFAULTS, ft as readStoredPinnedSessionKeys, g as RIGHT_SIDEBAR_FILE_TREE_WIDTH_STORAGE_KEY, gt as readStoredSessionDoneDesktopNotificationsEnabled, h as RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, ht as readStoredRightSidebarOpen, i as CENTER_MESSAGES_STORAGE_KEY, it as promptDraftKey, j as TitleTooltip, jt as themeFamilyFixedMode, k as THEME_FAMILIES, kt as themeColorModeLabel, l as KeyboardShortcutContent, lt as readStoredCollapsedDirectories, m as RECENT_DIRECTORIES_STORAGE_KEY, mt as readStoredRecentDirectories, n as APPLIED_THEME_STORAGE_KEY, nt as normalizeThemeFamily, o as DRAFT_DIRECTORY_STORAGE_KEY, ot as rawShellCommandTextFromTool, p as PINNED_SESSIONS_STORAGE_KEY, pt as readStoredPromptDraft, r as AUTO_SCROLL_ENABLED_STORAGE_KEY, rt as previewUrlForImage, s as HIDE_TOOL_BLOCKS_STORAGE_KEY, st as readStoredAutoScrollEnabled, tt as normalizeThemeColorMode, ut as readStoredDraftDirectory, v as RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY, vt as readStoredSidebarDirectories, w as SIDEBAR_DIRECTORIES_STORAGE_KEY, wt as rememberStoredTerminalTabs, x as Route, xt as readStoredTheme, y as RIGHT_SIDEBAR_OPEN_STORAGE_KEY, yt as readStoredTerminalPanelOpen } from "./routes-CuWg5WTI.mjs";
6
6
  import { i as useQuery, n as useIsMutating, o as useQueryClient, r as useIsFetching, t as useMutation } from "../_libs/tanstack__react-query.mjs";
7
7
  import { t as useHotkeys } from "../_libs/tanstack__react-hotkeys.mjs";
8
8
  import { a as matchesKeyboardEvent, i as batch, o as formatForDisplay, r as createStore } from "../_libs/@tanstack/hotkeys+[...].mjs";
@@ -22,7 +22,7 @@ import { t as remarkGfm } from "../_libs/remark-gfm.mjs";
22
22
  import { a as getBuiltInSpriteSheet, i as getBuiltInFileIconColor, n as FileTree, r as createFileTreeIconResolver, t as useFileTree } from "../_libs/@pierre/trees+[...].mjs";
23
23
  import { a as closestCenter, c as useDraggable, d as useSensors, f as CSS$1, i as PointerSensor, n as DragOverlay, t as DndContext, u as useSensor } from "../_libs/@dnd-kit/core+[...].mjs";
24
24
  import { a as verticalListSortingStrategy, i as useSortable, n as arrayMove, r as horizontalListSortingStrategy, t as SortableContext } from "../_libs/dnd-kit__sortable.mjs";
25
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-DGf2MDLT.js
25
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-DySm_W8N.js
26
26
  var import_react = /* @__PURE__ */ __toESM(require_react());
27
27
  var import_jsx_runtime = require_jsx_runtime();
28
28
  var MOBILE_BREAKPOINT = 768;
@@ -503,8 +503,9 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
503
503
  window.addEventListener("keydown", handleKeyDown);
504
504
  return () => window.removeEventListener("keydown", handleKeyDown);
505
505
  }, [toggleSidebarRef]);
506
+ const state = open ? "expanded" : "collapsed";
506
507
  const contextValue = {
507
- state: open ? "expanded" : "collapsed",
508
+ state,
508
509
  open,
509
510
  setOpen,
510
511
  isMobile,
@@ -522,6 +523,7 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
522
523
  value: contextValue,
523
524
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
524
525
  "data-slot": "sidebar-wrapper",
526
+ "data-sidebar-state": state,
525
527
  style: {
526
528
  ...style,
527
529
  "--sidebar-width": sidebarWidth,
@@ -6150,6 +6152,17 @@ function updateDirectoryIndexLoadingState(current, directories, loading) {
6150
6152
  }
6151
6153
  return changed ? next : current;
6152
6154
  }
6155
+ function normalizeOptimisticSidebarPreview(value) {
6156
+ const text = value.replace(/\s+/g, " ").trim();
6157
+ if (!text) return "";
6158
+ if (text.length <= 240) return text;
6159
+ return `${text.slice(0, 239).trimEnd()}…`;
6160
+ }
6161
+ function sidebarEntryMatchesIdentity(entry, identity) {
6162
+ const sessionId = identity.sessionId?.trim() || "";
6163
+ const sessionPath = identity.sessionPath?.trim() || "";
6164
+ return Boolean(sessionPath && entry.path === sessionPath || sessionId && entry.id === sessionId);
6165
+ }
6153
6166
  function upsertOptimisticSidebarSessionEntry(current, entry) {
6154
6167
  const directory = entry.cwd.trim();
6155
6168
  const optimisticId = entry.id.trim();
@@ -6175,6 +6188,40 @@ function upsertOptimisticSidebarSessionEntry(current, entry) {
6175
6188
  }
6176
6189
  };
6177
6190
  }
6191
+ function updateOptimisticSidebarSessionPreviewEntry(current, options) {
6192
+ const preview = normalizeOptimisticSidebarPreview(options.preview);
6193
+ if (!preview) return current;
6194
+ let changed = false;
6195
+ const next = { ...current };
6196
+ for (const [directory, snapshot] of Object.entries(current)) {
6197
+ const updatedEntries = [];
6198
+ let sessionsChanged = false;
6199
+ const sessions = snapshot.sessions.map((entry) => {
6200
+ if (!sidebarEntryMatchesIdentity(entry, options)) return entry;
6201
+ const nextEntry = {
6202
+ ...entry,
6203
+ modified: options.modified,
6204
+ lastUserMessageAt: options.modified,
6205
+ lastMessageAt: options.modified,
6206
+ lastMessagePreview: preview,
6207
+ streaming: true,
6208
+ unread: false
6209
+ };
6210
+ updatedEntries.push(nextEntry);
6211
+ sessionsChanged ||= nextEntry !== entry;
6212
+ return nextEntry;
6213
+ });
6214
+ if (!sessionsChanged) continue;
6215
+ const updatedKeys = new Set(updatedEntries.map(sessionListEntryKey));
6216
+ next[directory] = {
6217
+ ...snapshot,
6218
+ revision: `optimistic-preview:${options.modified}:${snapshot.revision}`,
6219
+ sessions: [...updatedEntries, ...sessions.filter((entry) => !updatedKeys.has(sessionListEntryKey(entry)))]
6220
+ };
6221
+ changed = true;
6222
+ }
6223
+ return changed ? next : current;
6224
+ }
6178
6225
  function removeOptimisticSidebarSessionEntry(current, optimisticId) {
6179
6226
  const targetId = optimisticId?.trim() || "";
6180
6227
  if (!targetId) return current;
@@ -10573,10 +10620,9 @@ var PICO_DIFF_COLOR_CSS = `
10573
10620
  --diffs-bg-deletion-emphasis-override: color-mix(in lab, var(--diffs-bg) 42%, var(--diffs-deletion-base));
10574
10621
  }
10575
10622
 
10576
- @media (max-width: 640px) {
10577
- [data-annotation-content] {
10578
- inline-size: min(100%, calc(100vw - var(--diffs-column-number-width, 0px) - 1rem));
10579
- }
10623
+ [data-overflow="wrap"] [data-annotation-content] {
10624
+ inline-size: var(--diffs-column-content-width, auto);
10625
+ max-inline-size: min(100%, calc(100vw - 1rem));
10580
10626
  }
10581
10627
 
10582
10628
  [data-line-type="change-addition"]:is(
@@ -10768,22 +10814,7 @@ function formatToolArgString(value) {
10768
10814
  return trimmed;
10769
10815
  }
10770
10816
  function rawShellCommandText(block) {
10771
- if (block.type !== "tool" || block.name !== "bash") return "";
10772
- if (typeof block.args === "string") {
10773
- const trimmed = block.args.trim();
10774
- if (!trimmed) return "";
10775
- try {
10776
- const parsed = JSON.parse(trimmed);
10777
- if (parsed && typeof parsed === "object" && "command" in parsed) {
10778
- const command = parsed.command;
10779
- return typeof command === "string" ? command.trim() : trimmed;
10780
- }
10781
- } catch {
10782
- return trimmed;
10783
- }
10784
- return trimmed;
10785
- }
10786
- return getToolArgText(normalizeToolArgs(block.args), "command") || toolCommandPreview(block);
10817
+ return block.type === "tool" ? rawShellCommandTextFromTool(block.name, block.args) : "";
10787
10818
  }
10788
10819
  function toolCallText(block) {
10789
10820
  if (block.type !== "tool") return "";
@@ -10817,64 +10848,12 @@ function toolOutputText(block) {
10817
10848
  if (block.isError) return "Tool failed with no output.";
10818
10849
  return "No output available.";
10819
10850
  }
10820
- var EXPLORE_TOOL_NAMES = new Set([
10821
- "read",
10822
- "grep",
10823
- "glob",
10824
- "find",
10825
- "ls",
10826
- "list",
10827
- "rg"
10828
- ]);
10829
- var EXPLORE_SHELL_COMMAND_NAMES = new Set([
10830
- "find",
10831
- "grep",
10832
- "ls",
10833
- "rg"
10834
- ]);
10835
- var SKIPPABLE_SHELL_COMMAND_NAMES = new Set([
10836
- "cd",
10837
- "export",
10838
- "pwd",
10839
- "set"
10840
- ]);
10841
- var SHELL_COMMAND_WRAPPER_NAMES = new Set([
10842
- "command",
10843
- "env",
10844
- "noglob",
10845
- "sudo",
10846
- "time"
10847
- ]);
10848
- function shellCommandNameFromSegment(segment) {
10849
- let text = segment.trim();
10850
- while (text) {
10851
- const assignmentMatch = text.match(/^[A-Za-z_][A-Za-z0-9_]*=\S+\s+/);
10852
- if (assignmentMatch) {
10853
- text = text.slice(assignmentMatch[0].length).trimStart();
10854
- continue;
10855
- }
10856
- const commandMatch = text.match(/^([./A-Za-z0-9_-]+)\b/);
10857
- if (!commandMatch) return "";
10858
- const commandPath = commandMatch[1] || "";
10859
- const commandName = pathBaseName$1(commandPath);
10860
- if (!SHELL_COMMAND_WRAPPER_NAMES.has(commandName)) return commandName;
10861
- text = text.slice(commandPath.length).trimStart();
10862
- }
10863
- return "";
10864
- }
10865
10851
  function exploreShellCommandName(block) {
10866
- if (block.type !== "tool" || block.name !== "bash") return "";
10867
- const segments = rawShellCommandText(block).split(/(?:&&|\|\||;)/);
10868
- for (const segment of segments) {
10869
- const commandName = shellCommandNameFromSegment(segment);
10870
- if (!commandName || SKIPPABLE_SHELL_COMMAND_NAMES.has(commandName)) continue;
10871
- return EXPLORE_SHELL_COMMAND_NAMES.has(commandName) ? commandName : "";
10872
- }
10873
- return "";
10852
+ return block.type === "tool" ? exploreShellCommandNameFromTool(block.name, block.args) : "";
10874
10853
  }
10875
10854
  function matchesExploreToolBlock(block) {
10876
10855
  if (block.type !== "tool") return false;
10877
- return block.category === "explore" || EXPLORE_TOOL_NAMES.has(block.name || "") || Boolean(exploreShellCommandName(block));
10856
+ return block.category === "explore" || toolCategoryFromTool(block.name, block.args) === "explore";
10878
10857
  }
10879
10858
  function isExploreToolBlock(block) {
10880
10859
  return matchesExploreToolBlock(block);
@@ -10882,7 +10861,10 @@ function isExploreToolBlock(block) {
10882
10861
  function isPendingUnclassifiedToolBlock(block) {
10883
10862
  if (block.type !== "tool" || !block.running || matchesExploreToolBlock(block)) return false;
10884
10863
  if (!block.name) return true;
10885
- if (block.name === "bash") return !rawShellCommandText(block);
10864
+ if (block.name === "bash") {
10865
+ const category = toolCategoryFromTool(block.name, block.args);
10866
+ return !rawShellCommandText(block) || category !== "explore" && (toolArgsAreIncompleteJsonObject(block.args) || toolCouldBecomeExploreFromTool(block.name, block.args));
10867
+ }
10886
10868
  return false;
10887
10869
  }
10888
10870
  function getToolArgNumber(args, key) {
@@ -11176,18 +11158,22 @@ function assistantMessagesModelLabel(items) {
11176
11158
  function assistantItemIsWorking(item) {
11177
11159
  return Boolean(item.streaming) || item.done === false || item.blocks.some((block) => block.type === "tool" && block.running);
11178
11160
  }
11161
+ function assistantItemIsStreaming(item) {
11162
+ return Boolean(item.streaming);
11163
+ }
11179
11164
  function assistantMessagesShellSnapshotFromBlocks(snapshot, blocks) {
11180
11165
  const anchorBlockIndex = blocks.findIndex(assistantBlockIsMessageJumpAnchor);
11181
11166
  const anchorBlock = anchorBlockIndex >= 0 ? blocks[anchorBlockIndex] : void 0;
11182
11167
  const copyText = assistantCopyTextFromItems(snapshot.items);
11183
11168
  const modelLabel = assistantMessagesModelLabel(snapshot.items);
11169
+ const streaming = snapshot.items.some(assistantItemIsStreaming);
11184
11170
  const working = snapshot.items.some(assistantItemIsWorking);
11185
- const hasFooter = Boolean(copyText.trim() || modelLabel || working);
11171
+ const hasFooter = Boolean(copyText.trim() || modelLabel);
11186
11172
  return {
11187
11173
  ...anchorBlock ? { anchorBlockKey: assistantBlockRenderKey(anchorBlock, anchorBlockIndex) } : {},
11188
11174
  hasBlocks: blocks.length > 0,
11189
11175
  hasFooter,
11190
- streaming: working,
11176
+ streaming,
11191
11177
  working,
11192
11178
  copyText,
11193
11179
  modelLabel
@@ -11676,7 +11662,7 @@ function PlainToolOutput({ isError = false, text }) {
11676
11662
  children: text
11677
11663
  });
11678
11664
  }
11679
- function scrollDistanceFromBottom(element) {
11665
+ function scrollDistanceFromBottom$1(element) {
11680
11666
  return element.scrollHeight - element.clientHeight - element.scrollTop;
11681
11667
  }
11682
11668
  function scrollElementToBottom(element) {
@@ -11700,7 +11686,7 @@ function useRunningToolAutoScroll({ contentKey, enabled }) {
11700
11686
  }, [contentKey, enabled]);
11701
11687
  const handleScroll = (event) => {
11702
11688
  if (!enabled) return;
11703
- autoScrollRef.current = scrollDistanceFromBottom(event.currentTarget) < 1;
11689
+ autoScrollRef.current = scrollDistanceFromBottom$1(event.currentTarget) < 1;
11704
11690
  };
11705
11691
  return {
11706
11692
  onScroll: handleScroll,
@@ -12006,53 +11992,67 @@ var UserMessageCard = import_react.memo(function UserMessageCard({ item }) {
12006
11992
  const selectedImage = selectedImageIndex === null ? null : item.images[selectedImageIndex] ?? null;
12007
11993
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12008
11994
  className: "mr-2 ml-auto w-fit max-w-[80%]",
12009
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12010
- className: "relative rounded-3xl bg-primary px-5 py-2.5 text-primary-foreground before:absolute before:-right-1.75 before:bottom-0 before:h-5.5 before:w-4 before:rounded-bl-[16px_14px] before:bg-primary before:content-[''] after:absolute after:-right-6.5 after:bottom-0 after:h-6.25 after:w-6.5 after:rounded-bl-[10px] after:bg-background after:content-['']",
12011
- children: [
12012
- labelText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12013
- className: "mb-2 flex items-center gap-2",
12014
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
12015
- variant: "outline",
12016
- className: "border-primary-foreground/35 bg-primary-foreground/10 text-primary-foreground",
12017
- children: labelText
12018
- })
12019
- }) : null,
12020
- item.text ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12021
- className: "text-sm break-words whitespace-pre-wrap text-primary-foreground",
12022
- children: item.text
12023
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12024
- className: "text-sm text-primary-foreground/80",
12025
- children: "Image prompt"
12026
- }),
12027
- item.images.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12028
- className: "mt-3 flex flex-wrap gap-3",
12029
- children: item.images.map((image, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
12030
- type: "button",
12031
- className: "max-w-full cursor-zoom-in overflow-hidden rounded-lg border border-primary-foreground/20 bg-primary-foreground/10 p-0 transition hover:opacity-90 focus-visible:ring-2 focus-visible:ring-primary-foreground/70 focus-visible:ring-offset-2 focus-visible:ring-offset-primary focus-visible:outline-none",
12032
- onClick: () => setSelectedImageIndex(index),
12033
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
12034
- src: image.previewUrl,
12035
- alt: "Prompt upload",
12036
- className: "block h-28 max-w-full object-cover"
12037
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12038
- className: "sr-only",
12039
- children: "Open prompt upload"
12040
- })]
12041
- }, `${promptImageKey(image)}:${index}`))
12042
- }) : null
12043
- ]
12044
- }), selectedImage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImageLightbox, {
12045
- open: true,
12046
- onOpenChange: (open) => {
12047
- if (!open) setSelectedImageIndex(null);
12048
- },
12049
- imageSrc: selectedImage.previewUrl,
12050
- imageAlt: "Prompt upload",
12051
- title: "Prompt upload preview",
12052
- description: "Expanded preview of an image attached to a user message."
12053
- }) : null]
11995
+ children: [
11996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11997
+ className: "relative rounded-3xl bg-primary px-5 py-2.5 text-primary-foreground before:absolute before:-right-1.75 before:bottom-0 before:h-5.5 before:w-4 before:rounded-bl-[16px_14px] before:bg-primary before:content-[''] after:absolute after:-right-6.5 after:bottom-0 after:h-6.25 after:w-6.5 after:rounded-bl-[10px] after:bg-background after:content-['']",
11998
+ children: [
11999
+ labelText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12000
+ className: "mb-2 flex items-center gap-2",
12001
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
12002
+ variant: "outline",
12003
+ className: "border-primary-foreground/35 bg-primary-foreground/10 text-primary-foreground",
12004
+ children: labelText
12005
+ })
12006
+ }) : null,
12007
+ item.text ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12008
+ className: "text-sm break-words whitespace-pre-wrap text-primary-foreground",
12009
+ children: item.text
12010
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12011
+ className: "text-sm text-primary-foreground/80",
12012
+ children: "Image prompt"
12013
+ }),
12014
+ item.images.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12015
+ className: "mt-3 flex flex-wrap gap-3",
12016
+ children: item.images.map((image, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
12017
+ type: "button",
12018
+ className: "max-w-full cursor-zoom-in overflow-hidden rounded-lg border border-primary-foreground/20 bg-primary-foreground/10 p-0 transition hover:opacity-90 focus-visible:ring-2 focus-visible:ring-primary-foreground/70 focus-visible:ring-offset-2 focus-visible:ring-offset-primary focus-visible:outline-none",
12019
+ onClick: () => setSelectedImageIndex(index),
12020
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
12021
+ src: image.previewUrl,
12022
+ alt: "Prompt upload",
12023
+ className: "block h-28 max-w-full object-cover"
12024
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12025
+ className: "sr-only",
12026
+ children: "Open prompt upload"
12027
+ })]
12028
+ }, `${promptImageKey(image)}:${index}`))
12029
+ }) : null
12030
+ ]
12031
+ }),
12032
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserMessageFooter, { copyText: item.text }),
12033
+ selectedImage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImageLightbox, {
12034
+ open: true,
12035
+ onOpenChange: (open) => {
12036
+ if (!open) setSelectedImageIndex(null);
12037
+ },
12038
+ imageSrc: selectedImage.previewUrl,
12039
+ imageAlt: "Prompt upload",
12040
+ title: "Prompt upload preview",
12041
+ description: "Expanded preview of an image attached to a user message."
12042
+ }) : null
12043
+ ]
12054
12044
  });
12055
12045
  });
12046
+ function UserMessageFooter({ copyText }) {
12047
+ if (!copyText.trim()) return null;
12048
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12049
+ className: "mt-1 flex justify-end gap-1.5 text-xs font-medium text-muted-foreground",
12050
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageFooterCopyButton, {
12051
+ idleLabel: "Copy user message",
12052
+ text: copyText
12053
+ })
12054
+ });
12055
+ }
12056
12056
  function assistantBlockIsVisible({ block, hideThinking, hideToolBlocks }) {
12057
12057
  switch (block.type) {
12058
12058
  case "text": return Boolean(block.text.trim());
@@ -12070,7 +12070,7 @@ function assistantMessageHasVisibleBlocks({ item, hideThinking, hideToolBlocks }
12070
12070
  }));
12071
12071
  }
12072
12072
  function assistantMessageHasFooterMeta(item) {
12073
- return Boolean(assistantItemIsWorking(item) || item.model || assistantCopyTextFromItems([item]));
12073
+ return Boolean(item.model || assistantCopyTextFromItems([item]));
12074
12074
  }
12075
12075
  var AssistantBlockGroupView = import_react.memo(function AssistantBlockGroupView({ anchorBlockKey, descriptor, store, streaming }) {
12076
12076
  if (descriptor.type === "explore") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantExploreBlockGroupView, {
@@ -12151,7 +12151,7 @@ function AssistantBlockGroupsView({ anchorBlockKey, store, streaming }) {
12151
12151
  streaming
12152
12152
  }, descriptor.key)) });
12153
12153
  }
12154
- function AssistantMessageFooterCopyButton({ text }) {
12154
+ function MessageFooterCopyButton({ className, idleLabel, text }) {
12155
12155
  const [copyState, setCopyState] = import_react.useState("idle");
12156
12156
  const canCopy = Boolean(text.trim());
12157
12157
  const copyMessage = async () => {
@@ -12165,13 +12165,13 @@ function AssistantMessageFooterCopyButton({ text }) {
12165
12165
  window.setTimeout(() => setCopyState("idle"), 1400);
12166
12166
  }
12167
12167
  };
12168
- const label = copyState === "copied" ? "Copied" : copyState === "error" ? "Retry copy" : "Copy assistant message";
12168
+ const label = copyState === "copied" ? "Copied" : copyState === "error" ? "Retry copy" : idleLabel;
12169
12169
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
12170
12170
  title: label,
12171
12171
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
12172
12172
  variant: "ghost",
12173
12173
  size: "icon-xs",
12174
- className: "-ml-2 text-muted-foreground",
12174
+ className: cn("text-muted-foreground", className),
12175
12175
  disabled: !canCopy,
12176
12176
  "aria-label": label,
12177
12177
  onClick: copyMessage,
@@ -12185,13 +12185,17 @@ function AssistantMessageFooter({ copyText, modelLabel, streaming }) {
12185
12185
  if (!showCopyButton && !modelLabel) return null;
12186
12186
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12187
12187
  className: "-mt-2 flex items-center gap-1.5 text-xs font-medium text-muted-foreground",
12188
- children: [showCopyButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantMessageFooterCopyButton, { text: copyText }) : null, modelLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12188
+ children: [showCopyButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageFooterCopyButton, {
12189
+ className: "-ml-2",
12190
+ idleLabel: "Copy assistant message",
12191
+ text: copyText
12192
+ }) : null, modelLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12189
12193
  className: "truncate",
12190
12194
  children: modelLabel
12191
12195
  }) : null]
12192
12196
  });
12193
12197
  }
12194
- function AssistantMessagesStoreCard({ hideFooter = false, store }) {
12198
+ function AssistantMessagesStoreCard({ className, hideFooter = false, store }) {
12195
12199
  const initialSnapshot = store.getSnapshot();
12196
12200
  const initialBlocks = visibleAssistantBlocksFromSnapshot(initialSnapshot);
12197
12201
  const assistantBlockStoreRef = import_react.useRef(null);
@@ -12215,20 +12219,21 @@ function AssistantMessagesStoreCard({ hideFooter = false, store }) {
12215
12219
  store
12216
12220
  ]);
12217
12221
  const shellSnapshot = import_react.useSyncExternalStore(shellStore.subscribe, shellStore.getSnapshot, shellStore.getSnapshot);
12218
- if (!shellSnapshot.hasBlocks && !shellSnapshot.hasFooter) return null;
12222
+ const showFooter = Boolean(!hideFooter && !shellSnapshot.working && shellSnapshot.hasFooter);
12223
+ if (!shellSnapshot.hasBlocks && !showFooter) return null;
12219
12224
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12220
- className: "flex flex-col gap-4",
12225
+ className: cn("flex flex-col gap-4", className),
12221
12226
  "data-conversation-assistant-group": "true",
12222
12227
  "data-conversation-streaming": shellSnapshot.streaming ? "true" : void 0,
12223
12228
  children: [shellSnapshot.hasBlocks ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantBlockGroupsView, {
12224
12229
  anchorBlockKey: shellSnapshot.anchorBlockKey,
12225
12230
  store: assistantBlockStore,
12226
12231
  streaming: shellSnapshot.streaming
12227
- }) : null, hideFooter ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantMessageFooter, {
12232
+ }) : null, showFooter ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantMessageFooter, {
12228
12233
  copyText: shellSnapshot.copyText,
12229
12234
  modelLabel: shellSnapshot.modelLabel,
12230
12235
  streaming: shellSnapshot.streaming
12231
- })]
12236
+ }) : null]
12232
12237
  });
12233
12238
  }
12234
12239
  function createSelectionStore(initialValue = 0) {
@@ -14543,6 +14548,78 @@ function useAppShellMessageScroll({ autoScrollEnabled, isSessionViewLoading, ses
14543
14548
  syncAfterConversationChange
14544
14549
  };
14545
14550
  }
14551
+ var DEFAULT_SCROLL_GRADIENT_MAX_HEIGHT = 4;
14552
+ var EMPTY_SCROLL_GRADIENT_STATE = {
14553
+ bottomHeight: 0,
14554
+ topHeight: 0
14555
+ };
14556
+ function scrollDistanceFromBottom(element) {
14557
+ return Math.max(0, element.scrollHeight - element.scrollTop - element.clientHeight);
14558
+ }
14559
+ function scrollGradientHeight(distanceFromEdge, maxHeight) {
14560
+ return Math.min(maxHeight, Math.max(0, distanceFromEdge));
14561
+ }
14562
+ function scrollGradientState(element, maxHeight) {
14563
+ if (!element) return EMPTY_SCROLL_GRADIENT_STATE;
14564
+ return {
14565
+ bottomHeight: scrollGradientHeight(scrollDistanceFromBottom(element), maxHeight),
14566
+ topHeight: scrollGradientHeight(element.scrollTop, maxHeight)
14567
+ };
14568
+ }
14569
+ function setScrollGradientState(setState, nextState) {
14570
+ setState((currentState) => currentState.bottomHeight === nextState.bottomHeight && currentState.topHeight === nextState.topHeight ? currentState : nextState);
14571
+ }
14572
+ function useScrollGradients({ disabled = false, maxHeight = DEFAULT_SCROLL_GRADIENT_MAX_HEIGHT } = {}) {
14573
+ const elementRef = import_react.useRef(null);
14574
+ const [state, setState] = import_react.useState(EMPTY_SCROLL_GRADIENT_STATE);
14575
+ const syncScrollGradients = import_react.useCallback((element = elementRef.current) => {
14576
+ setScrollGradientState(setState, disabled ? EMPTY_SCROLL_GRADIENT_STATE : scrollGradientState(element, maxHeight));
14577
+ }, [disabled, maxHeight]);
14578
+ const setScrollElement = import_react.useCallback((element) => {
14579
+ elementRef.current = element;
14580
+ syncScrollGradients(element);
14581
+ }, [syncScrollGradients]);
14582
+ const onScroll = import_react.useCallback((event) => {
14583
+ syncScrollGradients(event.currentTarget);
14584
+ }, [syncScrollGradients]);
14585
+ import_react.useLayoutEffect(() => {
14586
+ const element = elementRef.current;
14587
+ syncScrollGradients(element);
14588
+ if (!element || typeof ResizeObserver === "undefined") return;
14589
+ let animationFrame = 0;
14590
+ const scheduleSync = () => {
14591
+ window.cancelAnimationFrame(animationFrame);
14592
+ animationFrame = window.requestAnimationFrame(() => {
14593
+ animationFrame = 0;
14594
+ syncScrollGradients(elementRef.current);
14595
+ });
14596
+ };
14597
+ const resizeObserver = new ResizeObserver(scheduleSync);
14598
+ resizeObserver.observe(element);
14599
+ const content = element.firstElementChild;
14600
+ if (content instanceof HTMLElement) resizeObserver.observe(content);
14601
+ return () => {
14602
+ window.cancelAnimationFrame(animationFrame);
14603
+ resizeObserver.disconnect();
14604
+ };
14605
+ }, [syncScrollGradients]);
14606
+ return {
14607
+ bottomHeight: state.bottomHeight,
14608
+ onScroll,
14609
+ setScrollElement,
14610
+ syncScrollGradients,
14611
+ topHeight: state.topHeight
14612
+ };
14613
+ }
14614
+ function ScrollGradientOverlays({ bottomClassName, bottomHeight, className, topClassName, topHeight }) {
14615
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14616
+ className: cn("pointer-events-none absolute inset-x-0 top-0 z-10 bg-gradient-to-b from-black/10 to-transparent transition-[height,opacity] duration-150", topHeight > 0 ? "opacity-100" : "opacity-0", className, topClassName),
14617
+ style: { height: topHeight }
14618
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14619
+ className: cn("pointer-events-none absolute inset-x-0 bottom-0 z-10 bg-gradient-to-t from-black/10 to-transparent transition-[height,opacity] duration-150", bottomHeight > 0 ? "opacity-100" : "opacity-0", className, bottomClassName),
14620
+ style: { height: bottomHeight }
14621
+ })] });
14622
+ }
14546
14623
  function ConversationLatestMessageButton({ conversationItemsStore, draft, onClick, scrollStateStore }) {
14547
14624
  const hasMessages = useConversationHasMessages(conversationItemsStore);
14548
14625
  const isDisabled = useMessageScrollValue(scrollStateStore, (snapshot) => draft || !hasMessages || snapshot.isMessagesNearBottom);
@@ -14609,6 +14686,11 @@ function AppShellConversationFrame({ autoScrollEnabled, children, centerMessages
14609
14686
  isSessionViewLoading,
14610
14687
  sessionState
14611
14688
  });
14689
+ const { bottomHeight: conversationScrollBottomGradientHeight, onScroll: onConversationScrollGradientScroll, setScrollElement: setConversationScrollGradientElement, topHeight: conversationScrollTopGradientHeight } = useScrollGradients({ disabled: isSessionViewLoading });
14690
+ const setMessagesScrollAreaElement = import_react.useCallback((element) => {
14691
+ messagesScrollAreaRef.current = element;
14692
+ setConversationScrollGradientElement(element);
14693
+ }, [messagesScrollAreaRef, setConversationScrollGradientElement]);
14612
14694
  import_react.useImperativeHandle(ref, () => ({
14613
14695
  jumpToNextMessage,
14614
14696
  jumpToPreviousMessage,
@@ -14622,47 +14704,59 @@ function AppShellConversationFrame({ autoScrollEnabled, children, centerMessages
14622
14704
  ]);
14623
14705
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14624
14706
  className: "relative min-h-0 flex-1",
14625
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14626
- ref: messagesScrollAreaRef,
14627
- "data-conversation-viewport": "true",
14628
- tabIndex: 0,
14629
- role: "region",
14630
- "aria-label": "Conversation messages",
14631
- className: "h-full overflow-x-hidden overflow-y-auto overscroll-contain px-4 outline-none [overflow-anchor:none]",
14632
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14633
- ref: messagesContentRef,
14634
- className: "flex min-h-full flex-col",
14635
- children: [
14636
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationScrollRevisionObserver, {
14637
- conversationItemsStore,
14638
- disabled: isSessionViewLoading,
14639
- onRevisionChange: syncAfterConversationChange
14640
- }),
14641
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationContentChangeContext.Provider, {
14642
- value: syncAfterConversationChange,
14643
- children
14644
- }),
14645
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: bottomRef })
14646
- ]
14647
- })
14648
- }), !isSessionViewLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationLatestMessageButton, {
14649
- conversationItemsStore,
14650
- draft: sessionState.draft,
14651
- onClick: scrollConversationToBottom,
14652
- scrollStateStore
14653
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationPreviousMessageButton, {
14654
- centerMessages,
14655
- onClick: jumpToPreviousMessage,
14656
- scrollStateStore
14657
- })] }) : null]
14707
+ children: [
14708
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14709
+ ref: setMessagesScrollAreaElement,
14710
+ "data-conversation-viewport": "true",
14711
+ tabIndex: 0,
14712
+ role: "region",
14713
+ "aria-label": "Conversation messages",
14714
+ className: "h-full overflow-x-hidden overflow-y-auto overscroll-contain px-4 outline-none [overflow-anchor:none]",
14715
+ onScroll: onConversationScrollGradientScroll,
14716
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14717
+ ref: messagesContentRef,
14718
+ className: "flex min-h-full flex-col",
14719
+ children: [
14720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationScrollRevisionObserver, {
14721
+ conversationItemsStore,
14722
+ disabled: isSessionViewLoading,
14723
+ onRevisionChange: syncAfterConversationChange
14724
+ }),
14725
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationContentChangeContext.Provider, {
14726
+ value: syncAfterConversationChange,
14727
+ children
14728
+ }),
14729
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: bottomRef })
14730
+ ]
14731
+ })
14732
+ }),
14733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
14734
+ bottomHeight: conversationScrollBottomGradientHeight,
14735
+ topHeight: conversationScrollTopGradientHeight
14736
+ }),
14737
+ !isSessionViewLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationLatestMessageButton, {
14738
+ conversationItemsStore,
14739
+ draft: sessionState.draft,
14740
+ onClick: scrollConversationToBottom,
14741
+ scrollStateStore
14742
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationPreviousMessageButton, {
14743
+ centerMessages,
14744
+ onClick: jumpToPreviousMessage,
14745
+ scrollStateStore
14746
+ })] }) : null
14747
+ ]
14658
14748
  });
14659
14749
  }
14660
14750
  function AppShellWorkingIndicatorLabel({ fallbackLabel, hiddenThinkingPreviewStore, useHiddenThinkingPreview }) {
14661
14751
  const hiddenThinkingPreview = useSelector(hiddenThinkingPreviewStore);
14662
14752
  const visibleLabel = useHiddenThinkingPreview && hiddenThinkingPreview ? hiddenThinkingPreview : fallbackLabel;
14663
- if (!visibleLabel.trim()) return null;
14753
+ if (!visibleLabel.trim()) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14754
+ "aria-hidden": "true",
14755
+ className: "invisible leading-5 font-medium",
14756
+ children: "Working…"
14757
+ });
14664
14758
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14665
- className: "font-medium text-foreground",
14759
+ className: "leading-5 font-medium text-foreground",
14666
14760
  children: visibleLabel
14667
14761
  });
14668
14762
  }
@@ -14677,7 +14771,7 @@ function AppShellMessagesWorkingIndicator({ hiddenThinkingPreviewStore, onCancel
14677
14771
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14678
14772
  className: "min-w-0 flex-1",
14679
14773
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14680
- className: "flex min-w-0 items-center justify-start gap-2",
14774
+ className: "flex min-h-5 min-w-0 items-center justify-start gap-2",
14681
14775
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14682
14776
  className: "min-w-0 shrink-0",
14683
14777
  children: state.done ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -14764,12 +14858,10 @@ function ConversationAssistantGroupView({ className, groupKey, hideFooter, hideT
14764
14858
  store,
14765
14859
  syncAfterConversationContentChange
14766
14860
  ]);
14767
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14861
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantMessagesStoreCard, {
14768
14862
  className,
14769
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantMessagesStoreCard, {
14770
- hideFooter,
14771
- store: assistantMessagesStore
14772
- })
14863
+ hideFooter,
14864
+ store: assistantMessagesStore
14773
14865
  });
14774
14866
  }
14775
14867
  function AppShellConversationItemGroups({ centerMessages, conversationItemsStore, hideFooter, hideThinking, hideToolBlocks }) {
@@ -14839,14 +14931,13 @@ function AppShellConversationWorkingFooter({ centerMessages, conversationItemsSt
14839
14931
  }
14840
14932
  function AppShellConversationMessageStack({ centerMessages, conversationItemsStore, hideToolBlocks, sessionStore }) {
14841
14933
  const hideThinking = useSelector(sessionStore, (sessionState) => sessionState.hideThinkingBlock);
14842
- const streaming = useSelector(sessionStore, (sessionState) => sessionState.streaming);
14843
14934
  if (!useConversationHasMessages(conversationItemsStore)) return null;
14844
14935
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14845
14936
  className: "flex flex-col gap-4 pt-4",
14846
14937
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellConversationItemGroups, {
14847
14938
  centerMessages,
14848
14939
  conversationItemsStore,
14849
- hideFooter: streaming,
14940
+ hideFooter: false,
14850
14941
  hideThinking,
14851
14942
  hideToolBlocks
14852
14943
  })
@@ -16711,6 +16802,7 @@ function GitRepositorySummary({ viewerContextId, cwd, active, onBranchClick }) {
16711
16802
  }
16712
16803
  function GitPanelToolbar({ viewerContextId, cwd, active }) {
16713
16804
  const queryClient = useQueryClient();
16805
+ const isMobile = useIsMobile();
16714
16806
  const normalizedCwd = normalizeCwd(cwd);
16715
16807
  const [branchDialogOpen, setBranchDialogOpen] = import_react.useState(false);
16716
16808
  const [commitDialogOpen, setCommitDialogOpen] = import_react.useState(false);
@@ -16824,6 +16916,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16824
16916
  kbd: formatShortcutLabel("Control+C"),
16825
16917
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16826
16918
  variant: "outline",
16919
+ size: isMobile ? "default" : "sm",
16827
16920
  onClick: () => {
16828
16921
  setCommitDialogOpen(true);
16829
16922
  },
@@ -16835,6 +16928,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16835
16928
  kbd: formatShortcutLabel("Control+P"),
16836
16929
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16837
16930
  variant: "outline",
16931
+ size: isMobile ? "default" : "sm",
16838
16932
  disabled: gitActionBusy,
16839
16933
  onClick: () => {
16840
16934
  gitActionMutation.mutate("push");
@@ -16847,6 +16941,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16847
16941
  kbd: formatShortcutLabel("Control+Shift+P"),
16848
16942
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16849
16943
  variant: "outline",
16944
+ size: isMobile ? "default" : "sm",
16850
16945
  disabled: gitActionBusy,
16851
16946
  onClick: () => {
16852
16947
  gitActionMutation.mutate("force-push");
@@ -16859,6 +16954,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16859
16954
  kbd: formatShortcutLabel("Alt+P"),
16860
16955
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16861
16956
  variant: "outline",
16957
+ size: isMobile ? "default" : "sm",
16862
16958
  disabled: gitActionBusy,
16863
16959
  onClick: () => {
16864
16960
  gitActionMutation.mutate("pull");
@@ -16967,26 +17063,6 @@ function AlertDialogCancel({ className, variant = "outline", size = "default", .
16967
17063
  ...props
16968
17064
  });
16969
17065
  }
16970
- function setDerivedScrollState(setState, nextState) {
16971
- setState((current) => Object.is(current, nextState) ? current : nextState);
16972
- }
16973
- function hasScrolledContent(scrollElement) {
16974
- return Boolean(scrollElement && scrollElement.scrollTop > 0);
16975
- }
16976
- function hasRemainingScrollContent(scrollElement) {
16977
- if (!scrollElement) return false;
16978
- return scrollElement.scrollHeight - scrollElement.clientHeight - scrollElement.scrollTop > 1;
16979
- }
16980
- function getStuckScrollTriggerValue({ getValue, scrollElement, selector }) {
16981
- if (!scrollElement || scrollElement.scrollTop <= 0) return "";
16982
- const containerTop = scrollElement.getBoundingClientRect().top;
16983
- const triggers = Array.from(scrollElement.querySelectorAll(selector));
16984
- for (const trigger of triggers) {
16985
- const rect = trigger.getBoundingClientRect();
16986
- if (rect.top <= containerTop + 1 && rect.bottom > containerTop + 1) return getValue(trigger);
16987
- }
16988
- return "";
16989
- }
16990
17066
  function GitSection({ title, meta, controls, className, bodyClassName, children }) {
16991
17067
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
16992
17068
  className: cn("overflow-hidden rounded-xl border border-border/80 bg-card/50", className),
@@ -17030,10 +17106,7 @@ var initialGitCommitRowState = {
17030
17106
  formDialog: null,
17031
17107
  confirmDialog: null,
17032
17108
  contextMenuOpen: false,
17033
- contextMenuKey: 0,
17034
- scrollValue: "",
17035
- commitBodyHeaderShadowed: false,
17036
- commitBodyFooterShadowed: false
17109
+ contextMenuKey: 0
17037
17110
  };
17038
17111
  function gitCommitRowReducer(state, action) {
17039
17112
  switch (action.type) {
@@ -17081,18 +17154,10 @@ function gitCommitRowReducer(state, action) {
17081
17154
  contextMenuOpen: false,
17082
17155
  contextMenuKey: state.contextMenuKey + 1
17083
17156
  };
17084
- case "set-scroll-shadows":
17085
- if (state.scrollValue === action.value && state.commitBodyHeaderShadowed === action.headerShadowed && state.commitBodyFooterShadowed === action.footerShadowed) return state;
17086
- return {
17087
- ...state,
17088
- scrollValue: action.value,
17089
- commitBodyHeaderShadowed: action.headerShadowed,
17090
- commitBodyFooterShadowed: action.footerShadowed
17091
- };
17092
17157
  }
17093
17158
  }
17094
17159
  function gitGraphLaneColor(index, active = false) {
17095
- if (active) return "#f87171";
17160
+ if (active) return "var(--warning)";
17096
17161
  return GIT_GRAPH_LANE_COLORS[index % GIT_GRAPH_LANE_COLORS.length];
17097
17162
  }
17098
17163
  var GitCommitGraphBranch = class {
@@ -17717,8 +17782,8 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17717
17782
  const [rowState, dispatchRow] = import_react.useReducer(gitCommitRowReducer, initialGitCommitRowState);
17718
17783
  const hashCopiedResetRef = import_react.useRef(void 0);
17719
17784
  const messageCopiedResetRef = import_react.useRef(void 0);
17720
- const commitBodyScrollRef = import_react.useRef(null);
17721
17785
  const contextMenuActionsRef = import_react.useRef(null);
17786
+ const { bottomHeight: commitBodyBottomGradientHeight, onScroll: onCommitBodyGradientScroll, setScrollElement: setCommitBodyGradientElement, topHeight: commitBodyTopGradientHeight } = useScrollGradients();
17722
17787
  const title = parsed.subject.trim();
17723
17788
  const time = formatGitCommitDetailTime(parsed.relativeDate);
17724
17789
  const fullTime = formatGitCommitFullDate(parsed.fullDate);
@@ -17727,19 +17792,6 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17727
17792
  const shortHash = parsed.hash || parsed.fullHash.slice(0, 7);
17728
17793
  const canRunCommitAction = Boolean(viewerContextId && cwd && parsed.fullHash);
17729
17794
  const { hashCopied, messageCopied, formDialog, confirmDialog, contextMenuOpen, contextMenuKey } = rowState;
17730
- const commitBodyHeaderShadowed = rowState.scrollValue === value && rowState.commitBodyHeaderShadowed;
17731
- const commitBodyFooterShadowed = rowState.scrollValue === value && rowState.commitBodyFooterShadowed;
17732
- const updateCommitBodyScrollShadows = (scrollElement) => {
17733
- dispatchRow({
17734
- type: "set-scroll-shadows",
17735
- value,
17736
- headerShadowed: hasScrolledContent(scrollElement),
17737
- footerShadowed: hasRemainingScrollContent(scrollElement)
17738
- });
17739
- };
17740
- import_react.useLayoutEffect(() => {
17741
- updateCommitBodyScrollShadows(commitBodyScrollRef.current);
17742
- });
17743
17795
  import_react.useEffect(() => {
17744
17796
  return () => {
17745
17797
  if (typeof hashCopiedResetRef.current === "number") window.clearTimeout(hashCopiedResetRef.current);
@@ -17906,7 +17958,7 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17906
17958
  });
17907
17959
  };
17908
17960
  if (!parsed.hash && !parsed.subject) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
17909
- className: "flex h-8 max-w-full min-w-0 items-center font-mono text-[13px] leading-5",
17961
+ className: "flex h-8 max-w-full min-w-0 items-center text-[13px] leading-5",
17910
17962
  children: parsed.subject ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
17911
17963
  className: "min-w-0 flex-1 truncate text-foreground",
17912
17964
  children: parsed.subject
@@ -17914,7 +17966,7 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17914
17966
  });
17915
17967
  const trigger = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionTrigger, {
17916
17968
  headerClassName: "min-w-0",
17917
- className: cn("h-8 min-h-8 w-full items-center gap-2 rounded-none px-1.5 py-0 font-mono text-[13px] leading-5 font-normal transition-[background-color,box-shadow,color] hover:no-underline focus-visible:ring-0 aria-expanded:bg-muted/50 **:data-[slot=accordion-trigger-icon]:size-4"),
17969
+ className: cn("h-8 min-h-8 w-full items-center gap-2 rounded-none px-1.5 py-0 text-[13px] leading-5 font-normal transition-[background-color,box-shadow,color] hover:no-underline focus-visible:ring-0 aria-expanded:bg-muted/50 **:data-[slot=accordion-trigger-icon]:size-4"),
17918
17970
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
17919
17971
  className: "min-w-0 flex-1 truncate pr-8 text-foreground",
17920
17972
  style: { paddingLeft: graphWidth },
@@ -18056,122 +18108,119 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
18056
18108
  }),
18057
18109
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionContent, {
18058
18110
  className: "relative h-[200px] border-b border-border/70 bg-muted/50 p-0",
18059
- children: [
18060
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18061
- ref: commitBodyScrollRef,
18062
- className: "h-full overflow-y-auto pr-2 pb-2",
18063
- onScroll: (event) => {
18064
- updateCommitBodyScrollShadows(event.currentTarget);
18065
- },
18066
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18067
- className: "grid content-start gap-1.5 pt-1 font-mono text-xs leading-4",
18068
- style: { paddingLeft: graphWidth },
18069
- children: [
18070
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18071
- className: "flex min-w-0 flex-wrap items-center gap-1.5 font-sans",
18072
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
18111
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18112
+ ref: setCommitBodyGradientElement,
18113
+ className: "h-full overflow-y-auto pr-2 pb-2",
18114
+ onScroll: onCommitBodyGradientScroll,
18115
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18116
+ className: "grid content-start gap-1.5 pt-1 text-xs leading-4",
18117
+ style: { paddingLeft: graphWidth },
18118
+ children: [
18119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18120
+ className: "flex min-w-0 flex-wrap items-center gap-1.5 font-sans",
18121
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
18122
+ type: "button",
18123
+ size: "xs",
18124
+ variant: "outline",
18125
+ disabled: !canRunCommitAction,
18126
+ onClick: () => openDiff("commit"),
18127
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileDiff, {}), "View diff"]
18128
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitActionsMenu, { ...commitActionProps })]
18129
+ }),
18130
+ parsed.author || time || parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18131
+ className: "flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 text-muted-foreground",
18132
+ children: [
18133
+ parsed.author ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: parsed.author }) : null,
18134
+ parsed.author && time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18135
+ "aria-hidden": true,
18136
+ children: "·"
18137
+ }) : null,
18138
+ time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18139
+ title: fullTime,
18140
+ side: "top",
18141
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: time })
18142
+ }) : null,
18143
+ (parsed.author || time) && parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18144
+ "aria-hidden": true,
18145
+ children: "·"
18146
+ }) : null,
18147
+ parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18148
+ className: "inline-flex min-w-0 items-center gap-1.5",
18149
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18150
+ className: "min-w-0 truncate",
18151
+ children: shortHash
18152
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18153
+ title: hashCopied ? "Copied" : "Copy full commit hash",
18154
+ side: "top",
18155
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18156
+ type: "button",
18157
+ "aria-label": "Copy full commit hash",
18158
+ className: "inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
18159
+ onClick: (event) => {
18160
+ event.preventDefault();
18161
+ event.stopPropagation();
18162
+ copyFullHashInline();
18163
+ },
18164
+ children: hashCopied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "size-3.5" })
18165
+ })
18166
+ })]
18167
+ }) : null
18168
+ ]
18169
+ }) : null,
18170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18171
+ className: "max-w-full break-words whitespace-normal text-foreground",
18172
+ children: [title || "No commit message", title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18173
+ title: messageCopied ? "Copied" : "Copy commit message",
18174
+ side: "top",
18175
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18073
18176
  type: "button",
18074
- size: "xs",
18075
- variant: "outline",
18076
- disabled: !canRunCommitAction,
18077
- onClick: () => openDiff("commit"),
18078
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileDiff, {}), "View diff"]
18079
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitActionsMenu, { ...commitActionProps })]
18080
- }),
18081
- parsed.author || time || parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18082
- className: "flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 text-muted-foreground",
18177
+ "aria-label": "Copy commit message",
18178
+ className: "ml-1 inline-flex size-4 items-center justify-center rounded-sm align-middle text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
18179
+ onClick: (event) => {
18180
+ event.preventDefault();
18181
+ event.stopPropagation();
18182
+ copyCommitMessageInline();
18183
+ },
18184
+ children: messageCopied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "size-3.5" })
18185
+ })
18186
+ }) : null]
18187
+ }),
18188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18189
+ className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1",
18190
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18191
+ className: "text-emerald-500",
18083
18192
  children: [
18084
- parsed.author ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: parsed.author }) : null,
18085
- parsed.author && time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18086
- "aria-hidden": true,
18087
- children: "·"
18088
- }) : null,
18089
- time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18090
- title: fullTime,
18091
- side: "top",
18092
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: time })
18093
- }) : null,
18094
- (parsed.author || time) && parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18095
- "aria-hidden": true,
18096
- children: "·"
18097
- }) : null,
18098
- parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18099
- className: "inline-flex min-w-0 items-center gap-1.5",
18100
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18101
- className: "min-w-0 truncate",
18102
- children: shortHash
18103
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18104
- title: hashCopied ? "Copied" : "Copy full commit hash",
18105
- side: "top",
18106
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18107
- type: "button",
18108
- "aria-label": "Copy full commit hash",
18109
- className: "inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
18110
- onClick: (event) => {
18111
- event.preventDefault();
18112
- event.stopPropagation();
18113
- copyFullHashInline();
18114
- },
18115
- children: hashCopied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "size-3.5" })
18116
- })
18117
- })]
18118
- }) : null
18193
+ "+",
18194
+ insertions,
18195
+ " addition",
18196
+ insertions === 1 ? "" : "s"
18119
18197
  ]
18120
- }) : null,
18121
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18122
- className: "max-w-full break-words whitespace-normal text-foreground",
18123
- children: [title || "No commit message", title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18124
- title: messageCopied ? "Copied" : "Copy commit message",
18125
- side: "top",
18126
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18127
- type: "button",
18128
- "aria-label": "Copy commit message",
18129
- className: "ml-1 inline-flex size-4 items-center justify-center rounded-sm align-middle text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
18130
- onClick: (event) => {
18131
- event.preventDefault();
18132
- event.stopPropagation();
18133
- copyCommitMessageInline();
18134
- },
18135
- children: messageCopied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "size-3.5" })
18136
- })
18137
- }) : null]
18138
- }),
18139
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18140
- className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1",
18141
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18142
- className: "text-emerald-500",
18143
- children: [
18144
- "+",
18145
- insertions,
18146
- " addition",
18147
- insertions === 1 ? "" : "s"
18148
- ]
18149
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18150
- className: "text-red-400",
18151
- children: [
18152
- "-",
18153
- deletions,
18154
- " deletion",
18155
- deletions === 1 ? "" : "s"
18156
- ]
18157
- })]
18158
- }),
18159
- parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitFilesList, {
18160
- viewerContextId,
18161
- cwd,
18162
- commit: parsed.fullHash,
18163
- shortHash,
18164
- previousShortHash: parsed.parents[0]?.slice(0, 7) || "",
18165
- subject: title,
18166
- disabled: !canRunCommitAction,
18167
- onOpenCommitDiff
18168
- }) : null
18169
- ]
18170
- })
18171
- }),
18172
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("pointer-events-none absolute inset-x-0 top-0 h-1 bg-gradient-to-b from-black/10 to-transparent transition-opacity duration-150", commitBodyHeaderShadowed ? "opacity-100" : "opacity-0") }),
18173
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("pointer-events-none absolute inset-x-0 bottom-0 h-1 bg-gradient-to-t from-black/10 to-transparent transition-opacity duration-150", commitBodyFooterShadowed ? "opacity-100" : "opacity-0") })
18174
- ]
18198
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18199
+ className: "text-red-400",
18200
+ children: [
18201
+ "-",
18202
+ deletions,
18203
+ " deletion",
18204
+ deletions === 1 ? "" : "s"
18205
+ ]
18206
+ })]
18207
+ }),
18208
+ parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitFilesList, {
18209
+ viewerContextId,
18210
+ cwd,
18211
+ commit: parsed.fullHash,
18212
+ shortHash,
18213
+ previousShortHash: parsed.parents[0]?.slice(0, 7) || "",
18214
+ subject: title,
18215
+ disabled: !canRunCommitAction,
18216
+ onOpenCommitDiff
18217
+ }) : null
18218
+ ]
18219
+ })
18220
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
18221
+ bottomHeight: commitBodyBottomGradientHeight,
18222
+ topHeight: commitBodyTopGradientHeight
18223
+ })]
18175
18224
  }),
18176
18225
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
18177
18226
  open: formDialog !== null,
@@ -18422,6 +18471,22 @@ function GitCommitsSection({ viewerContextId, cwd, active, onOpenCommitDiff, onR
18422
18471
  children: content
18423
18472
  });
18424
18473
  }
18474
+ function setDerivedScrollState(setState, nextState) {
18475
+ setState((current) => Object.is(current, nextState) ? current : nextState);
18476
+ }
18477
+ function hasScrolledContent(scrollElement) {
18478
+ return Boolean(scrollElement && scrollElement.scrollTop > 0);
18479
+ }
18480
+ function getStuckScrollTriggerValue({ getValue, scrollElement, selector }) {
18481
+ if (!scrollElement || scrollElement.scrollTop <= 0) return "";
18482
+ const containerTop = scrollElement.getBoundingClientRect().top;
18483
+ const triggers = Array.from(scrollElement.querySelectorAll(selector));
18484
+ for (const trigger of triggers) {
18485
+ const rect = trigger.getBoundingClientRect();
18486
+ if (rect.top <= containerTop + 1 && rect.bottom > containerTop + 1) return getValue(trigger);
18487
+ }
18488
+ return "";
18489
+ }
18425
18490
  function GitFileStatus({ status }) {
18426
18491
  const [indexCharacter, worktreeCharacter] = gitFileStatusCharacters(status);
18427
18492
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -18461,6 +18526,30 @@ function GitFileDiff({ file }) {
18461
18526
  }) : null]
18462
18527
  });
18463
18528
  }
18529
+ function gitFileCanStage(file) {
18530
+ const [indexStatus, worktreeStatus] = gitFileStatusCharacters(file.status);
18531
+ return worktreeStatus !== " " || indexStatus === "?";
18532
+ }
18533
+ function gitFileCanUnstage(file) {
18534
+ const [indexStatus] = gitFileStatusCharacters(file.status);
18535
+ return indexStatus !== " " && indexStatus !== "?";
18536
+ }
18537
+ function reviewBulkGitActionSuccessLabel(action) {
18538
+ switch (action) {
18539
+ case "discard-all": return "Discarded all changes";
18540
+ case "nuke-working-tree": return "Nuked working tree";
18541
+ case "unstage-all": return "Unstaged all changes";
18542
+ case "stage-all": return "Staged all changes";
18543
+ }
18544
+ }
18545
+ function reviewBulkGitActionErrorFallback(action) {
18546
+ switch (action) {
18547
+ case "discard-all": return "Failed to discard changes";
18548
+ case "nuke-working-tree": return "Failed to nuke working tree";
18549
+ case "unstage-all": return "Failed to unstage changes";
18550
+ case "stage-all": return "Failed to stage changes";
18551
+ }
18552
+ }
18464
18553
  function reviewDiffCommentSideLabel(side) {
18465
18554
  return side === "deletions" ? "old" : "new";
18466
18555
  }
@@ -18501,7 +18590,7 @@ function ReviewDiffPendingLineComment({ comment }) {
18501
18590
  comment.onSubmit(normalizedText);
18502
18591
  };
18503
18592
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
18504
- className: "m-2 w-[min(38rem,calc(100vw_-_5rem))] max-w-[calc(100%_-_1rem)] rounded-lg border border-border/70 bg-background p-2 shadow-sm",
18593
+ className: "m-2 w-[calc(100%_-_1rem)] max-w-[38rem] rounded-lg border border-border/70 bg-background p-2 shadow-sm",
18505
18594
  onPointerDown: (event) => event.stopPropagation(),
18506
18595
  onSubmit: (event) => {
18507
18596
  event.preventDefault();
@@ -18680,6 +18769,7 @@ function FileReviewContent(props) {
18680
18769
  }
18681
18770
  function useFileReviewContentView({ viewerContextId, cwd, active, diffLineComments = [], onAddDiffLineComment, onMaximizeHistory, onOpenCommitDiff, onOpenFile, showEmbeddedHistory = true }) {
18682
18771
  const normalizedCwd = normalizeCwd(cwd);
18772
+ const queryClient = useQueryClient();
18683
18773
  const isMobile = useIsMobile();
18684
18774
  const [state, dispatch] = import_react.useReducer(fileReviewReducer, initialFileReviewState, (initialState) => ({
18685
18775
  ...initialState,
@@ -18700,6 +18790,11 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18700
18790
  });
18701
18791
  };
18702
18792
  const changesScrollRef = import_react.useRef(null);
18793
+ const { bottomHeight: changesScrollBottomGradientHeight, onScroll: onChangesScrollGradientScroll, setScrollElement: setChangesScrollGradientElement, topHeight: changesScrollTopGradientHeight } = useScrollGradients();
18794
+ const setChangesScrollElement = import_react.useCallback((element) => {
18795
+ changesScrollRef.current = element;
18796
+ setChangesScrollGradientElement(element);
18797
+ }, [setChangesScrollGradientElement]);
18703
18798
  const reviewContentRef = import_react.useRef(null);
18704
18799
  const historyPanelRef = import_react.useRef(null);
18705
18800
  const historyScrollRef = import_react.useRef(null);
@@ -18720,14 +18815,41 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18720
18815
  });
18721
18816
  const files = filesQuery.data;
18722
18817
  const changedFiles = Array.isArray(files) ? files : [];
18723
- import_react.useEffect(() => {
18724
- if (previousNormalizedCwdRef.current === normalizedCwd) return;
18725
- previousNormalizedCwdRef.current = normalizedCwd;
18726
- dispatch({
18727
- type: "cwdChanged",
18728
- historyPanelHeight: readStoredGitHistoryPanelHeight()
18729
- });
18730
- }, [normalizedCwd]);
18818
+ const canStageAll = changedFiles.some(gitFileCanStage);
18819
+ const canUnstageAll = changedFiles.some(gitFileCanUnstage);
18820
+ const canDiscardAll = changedFiles.length > 0;
18821
+ const [confirmNukeWorkingTreeOpen, setConfirmNukeWorkingTreeOpen] = import_react.useState(false);
18822
+ const bulkGitActionMutation = useMutation({
18823
+ mutationFn: async (action) => {
18824
+ return await fetchJson(buildRequestUrl(action === "discard-all" || action === "nuke-working-tree" ? "/api/git-discard" : "/api/git-stage", { contextId: viewerContextId }), {
18825
+ method: "POST",
18826
+ headers: { "content-type": "application/json" },
18827
+ body: JSON.stringify({
18828
+ action,
18829
+ cwd: normalizedCwd
18830
+ })
18831
+ });
18832
+ },
18833
+ onSuccess: async (_response, action) => {
18834
+ await invalidateGitQueries({
18835
+ queryClient,
18836
+ viewerContextId,
18837
+ cwd: normalizedCwd
18838
+ });
18839
+ toast.success(reviewBulkGitActionSuccessLabel(action));
18840
+ },
18841
+ onError: (error, action) => {
18842
+ toast.error(getErrorMessage(error, reviewBulkGitActionErrorFallback(action)));
18843
+ }
18844
+ });
18845
+ import_react.useEffect(() => {
18846
+ if (previousNormalizedCwdRef.current === normalizedCwd) return;
18847
+ previousNormalizedCwdRef.current = normalizedCwd;
18848
+ dispatch({
18849
+ type: "cwdChanged",
18850
+ historyPanelHeight: readStoredGitHistoryPanelHeight()
18851
+ });
18852
+ }, [normalizedCwd]);
18731
18853
  const updateStickyReviewFileHeader = (scrollElement) => {
18732
18854
  setDerivedScrollState(setStickyReviewFileValue, getStuckScrollTriggerValue({
18733
18855
  getValue: (trigger) => trigger.dataset.reviewFileValue || "",
@@ -18822,12 +18944,28 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18822
18944
  normalizedCwd
18823
18945
  ]);
18824
18946
  const hasOpenFile = openFiles.length > 0;
18947
+ const bulkActionPending = bulkGitActionMutation.isPending;
18948
+ const bulkActionDisabled = bulkActionPending || !viewerContextId || !normalizedCwd || typeof files === "undefined" || files === null || Boolean(filesQuery.error);
18949
+ const discardAllPending = bulkGitActionMutation.isPending && bulkGitActionMutation.variables === "discard-all";
18950
+ const nukeWorkingTreePending = bulkGitActionMutation.isPending && bulkGitActionMutation.variables === "nuke-working-tree";
18951
+ const stageAllPending = bulkGitActionMutation.isPending && bulkGitActionMutation.variables === "stage-all";
18952
+ const unstageAllPending = bulkGitActionMutation.isPending && bulkGitActionMutation.variables === "unstage-all";
18825
18953
  const toggleAll = () => {
18826
18954
  dispatch({
18827
18955
  type: "openFilesChanged",
18828
18956
  openFiles: hasOpenFile ? [] : changedFiles.map(reviewFileValue)
18829
18957
  });
18830
18958
  };
18959
+ const runBulkGitAction = (action) => {
18960
+ bulkGitActionMutation.mutate(action);
18961
+ };
18962
+ const requestNukeWorkingTree = () => {
18963
+ setConfirmNukeWorkingTreeOpen(true);
18964
+ };
18965
+ const runConfirmedNukeWorkingTree = () => {
18966
+ bulkGitActionMutation.mutate("nuke-working-tree");
18967
+ setConfirmNukeWorkingTreeOpen(false);
18968
+ };
18831
18969
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18832
18970
  ref: reviewContentRef,
18833
18971
  className: "flex h-full min-h-0 flex-col bg-background",
@@ -18850,82 +18988,130 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18850
18988
  children: "Diffs"
18851
18989
  })
18852
18990
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18853
- className: "flex shrink-0 items-center gap-2",
18854
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18855
- title: hasOpenFile ? "Collapse all" : "Expand all",
18856
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
18991
+ className: "flex shrink-0 flex-wrap items-center justify-end gap-2",
18992
+ children: [
18993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuTrigger, {
18994
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
18995
+ variant: "outline",
18996
+ size: isMobile ? "icon" : "icon-sm",
18997
+ "aria-label": "Diff actions",
18998
+ disabled: bulkActionDisabled
18999
+ }),
19000
+ children: bulkActionPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ellipsis, { className: "size-4" })
19001
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuContent, {
19002
+ align: "end",
19003
+ className: "w-52",
19004
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuGroup, { children: [
19005
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
19006
+ disabled: bulkActionDisabled || !canStageAll,
19007
+ onClick: () => runBulkGitAction("stage-all"),
19008
+ children: [stageAllPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "Stage all"]
19009
+ }),
19010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
19011
+ disabled: bulkActionDisabled || !canUnstageAll,
19012
+ onClick: () => runBulkGitAction("unstage-all"),
19013
+ children: [unstageAllPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, {}), "Unstage all"]
19014
+ }),
19015
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
19016
+ variant: "destructive",
19017
+ disabled: bulkActionDisabled || !canDiscardAll,
19018
+ onClick: () => runBulkGitAction("discard-all"),
19019
+ children: [discardAllPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, {}), "Discard all"]
19020
+ }),
19021
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuSeparator, {}),
19022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
19023
+ variant: "destructive",
19024
+ disabled: bulkActionDisabled,
19025
+ onClick: requestNukeWorkingTree,
19026
+ children: [nukeWorkingTreePending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, {}), "Nuke working tree"]
19027
+ })
19028
+ ] })
19029
+ })] }),
19030
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
19031
+ title: hasOpenFile ? "Collapse all" : "Expand all",
19032
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
19033
+ variant: "outline",
19034
+ size: isMobile ? "icon" : "icon-sm",
19035
+ "aria-label": hasOpenFile ? "Collapse all" : "Expand all",
19036
+ disabled: changedFiles.length === 0,
19037
+ onClick: toggleAll,
19038
+ children: hasOpenFile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronsDownUp, { className: "size-4" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronsUpDown, { className: "size-4" })
19039
+ })
19040
+ }),
19041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleGroup$1, {
18857
19042
  variant: "outline",
18858
- size: "icon",
18859
- "aria-label": hasOpenFile ? "Collapse all" : "Expand all",
18860
- disabled: changedFiles.length === 0,
18861
- onClick: toggleAll,
18862
- children: hasOpenFile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronsDownUp, { className: "size-4" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronsUpDown, { className: "size-4" })
19043
+ size: isMobile ? "default" : "sm",
19044
+ value: [diffStyle],
19045
+ onValueChange: (values) => {
19046
+ const value = values[0];
19047
+ if (value === "unified" || value === "split") dispatch({
19048
+ type: "diffStyleChanged",
19049
+ diffStyle: value
19050
+ });
19051
+ },
19052
+ children: ["unified", "split"].map((value) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleGroupItem, {
19053
+ value,
19054
+ children: value === "unified" ? "Unified" : "Split"
19055
+ }, value))
18863
19056
  })
18864
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleGroup$1, {
18865
- variant: "outline",
18866
- value: [diffStyle],
18867
- onValueChange: (values) => {
18868
- const value = values[0];
18869
- if (value === "unified" || value === "split") dispatch({
18870
- type: "diffStyleChanged",
18871
- diffStyle: value
18872
- });
18873
- },
18874
- children: ["unified", "split"].map((value) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleGroupItem, {
18875
- value,
18876
- children: value === "unified" ? "Unified" : "Split"
18877
- }, value))
18878
- })]
19057
+ ]
18879
19058
  })]
18880
19059
  }),
18881
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18882
- ref: changesScrollRef,
18883
- className: "min-h-0 flex-1 overflow-auto",
18884
- onScroll: (event) => {
18885
- updateStickyReviewFileHeader(event.currentTarget);
18886
- },
18887
- children: !normalizedCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
18888
- className: "px-3 py-2.5",
18889
- children: "No directory selected."
18890
- }) : !viewerContextId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
18891
- className: "px-3 py-2.5",
18892
- children: "Waiting for viewer context…"
18893
- }) : filesQuery.isPending && typeof files === "undefined" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, {
18894
- className: "px-3 py-2.5",
18895
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading changes…"]
18896
- }) : filesQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
18897
- tone: "destructive",
18898
- className: "px-3 py-2.5",
18899
- children: getErrorMessage(filesQuery.error, "Failed to load changes")
18900
- }) : files === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
18901
- className: "px-3 py-2.5",
18902
- children: "No git repository detected."
18903
- }) : changedFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Accordion$1, {
18904
- multiple: true,
18905
- value: openFiles,
18906
- onValueChange: (nextOpenFiles) => {
18907
- dispatch({
18908
- type: "openFilesChanged",
18909
- openFiles: nextOpenFiles
18910
- });
19060
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19061
+ className: "relative min-h-0 flex-1",
19062
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19063
+ ref: setChangesScrollElement,
19064
+ className: "h-full overflow-auto",
19065
+ onScroll: (event) => {
19066
+ updateStickyReviewFileHeader(event.currentTarget);
19067
+ onChangesScrollGradientScroll(event);
18911
19068
  },
18912
- className: "border-b border-border/80",
18913
- children: changedFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewFileAccordionItem, {
18914
- viewerContextId,
18915
- cwd: normalizedCwd,
18916
- active,
18917
- diffLineComments,
18918
- diffStyle,
18919
- file,
18920
- open: openFiles.includes(reviewFileValue(file)),
18921
- stuck: stickyReviewFileValue === reviewFileValue(file),
18922
- onAddDiffLineComment,
18923
- onOpenFile
18924
- }, reviewFileValue(file)))
18925
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
18926
- className: "px-3 py-2.5",
18927
- children: "Working tree clean."
18928
- })
19069
+ children: !normalizedCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19070
+ className: "px-3 py-2.5",
19071
+ children: "No directory selected."
19072
+ }) : !viewerContextId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19073
+ className: "px-3 py-2.5",
19074
+ children: "Waiting for viewer context…"
19075
+ }) : filesQuery.isPending && typeof files === "undefined" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, {
19076
+ className: "px-3 py-2.5",
19077
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading changes…"]
19078
+ }) : filesQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19079
+ tone: "destructive",
19080
+ className: "px-3 py-2.5",
19081
+ children: getErrorMessage(filesQuery.error, "Failed to load changes")
19082
+ }) : files === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19083
+ className: "px-3 py-2.5",
19084
+ children: "No git repository detected."
19085
+ }) : changedFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Accordion$1, {
19086
+ multiple: true,
19087
+ value: openFiles,
19088
+ onValueChange: (nextOpenFiles) => {
19089
+ dispatch({
19090
+ type: "openFilesChanged",
19091
+ openFiles: nextOpenFiles
19092
+ });
19093
+ },
19094
+ className: "border-b border-border/80",
19095
+ children: changedFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewFileAccordionItem, {
19096
+ viewerContextId,
19097
+ cwd: normalizedCwd,
19098
+ active,
19099
+ diffLineComments,
19100
+ diffStyle,
19101
+ file,
19102
+ open: openFiles.includes(reviewFileValue(file)),
19103
+ stuck: stickyReviewFileValue === reviewFileValue(file),
19104
+ onAddDiffLineComment,
19105
+ onOpenFile
19106
+ }, reviewFileValue(file)))
19107
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19108
+ className: "px-3 py-2.5",
19109
+ children: "Working tree clean."
19110
+ })
19111
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
19112
+ bottomHeight: changesScrollBottomGradientHeight,
19113
+ topHeight: changesScrollTopGradientHeight
19114
+ })]
18929
19115
  }),
18930
19116
  showEmbeddedHistory && normalizedCwd && viewerContextId && files !== null && !filesQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18931
19117
  ref: historyPanelRef,
@@ -18984,7 +19170,21 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18984
19170
  })
18985
19171
  }) : null
18986
19172
  ]
18987
- }) : null
19173
+ }) : null,
19174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialog$1, {
19175
+ open: confirmNukeWorkingTreeOpen,
19176
+ onOpenChange: setConfirmNukeWorkingTreeOpen,
19177
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogHeader, { children: [
19178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogMedia, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {}) }),
19179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogTitle, { children: "Nuke working tree?" }),
19180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogDescription, { children: "This restores tracked files to HEAD and deletes untracked and ignored files in this repository. This cannot be undone from Pico." })
19181
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, { children: "Cancel" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
19182
+ variant: "destructive",
19183
+ disabled: bulkActionDisabled,
19184
+ onClick: runConfirmedNukeWorkingTree,
19185
+ children: "Nuke working tree"
19186
+ })] })] })
19187
+ })
18988
19188
  ]
18989
19189
  });
18990
19190
  }
@@ -19061,8 +19261,8 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19061
19261
  onGutterUtilityClick: startLineComment
19062
19262
  } : {};
19063
19263
  const themeOptions = usePicoDiffThemeOptions();
19064
- const [indexStatus, worktreeStatus] = gitFileStatusCharacters(file.status);
19065
- const canStage = worktreeStatus !== " " || indexStatus === "?";
19264
+ const canStage = gitFileCanStage(file);
19265
+ const canUnstage = gitFileCanUnstage(file);
19066
19266
  const invalidateChangedFileQueries = async () => {
19067
19267
  await invalidateGitQueries({
19068
19268
  queryClient,
@@ -19071,26 +19271,27 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19071
19271
  });
19072
19272
  };
19073
19273
  const stageMutation = useMutation({
19074
- mutationFn: async () => await fetchJson(buildRequestUrl("/api/git-stage", { contextId: viewerContextId }), {
19274
+ mutationFn: async (action) => await fetchJson(buildRequestUrl("/api/git-stage", { contextId: viewerContextId }), {
19075
19275
  method: "POST",
19076
19276
  headers: { "content-type": "application/json" },
19077
19277
  body: JSON.stringify({
19278
+ action,
19078
19279
  cwd: normalizedCwd,
19079
19280
  path: file.path,
19080
19281
  previousPath: file.previousPath
19081
19282
  })
19082
19283
  }),
19083
- onSuccess: async () => {
19284
+ onSuccess: async (_response, action) => {
19084
19285
  await queryClient.invalidateQueries({
19085
19286
  queryKey: picoQueryKeys.gitStatus(viewerContextId, normalizedCwd),
19086
19287
  exact: true,
19087
19288
  refetchType: "active"
19088
19289
  });
19089
19290
  await invalidateChangedFileQueries();
19090
- toast.success("Staged changes", { description: file.path });
19291
+ toast.success(action === "unstage" ? "Unstaged changes" : "Staged changes", { description: file.path });
19091
19292
  },
19092
- onError: (error) => {
19093
- toast.error(getErrorMessage(error, "Failed to stage changes"));
19293
+ onError: (error, action) => {
19294
+ toast.error(getErrorMessage(error, action === "unstage" ? "Failed to unstage changes" : "Failed to stage changes"));
19094
19295
  }
19095
19296
  });
19096
19297
  const discardMutation = useMutation({
@@ -19127,6 +19328,9 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19127
19328
  oldContent,
19128
19329
  newContent
19129
19330
  ]);
19331
+ const gitActionDisabled = actionPending || !viewerContextId || !normalizedCwd;
19332
+ const stagePending = stageMutation.isPending && stageMutation.variables === "stage";
19333
+ const unstagePending = stageMutation.isPending && stageMutation.variables === "unstage";
19130
19334
  const openFile = (event) => {
19131
19335
  event?.stopPropagation();
19132
19336
  onOpenFile?.(file.path, { pin: true });
@@ -19137,50 +19341,81 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19137
19341
  };
19138
19342
  const stageFileChanges = (event) => {
19139
19343
  event?.stopPropagation();
19140
- stageMutation.mutate();
19344
+ stageMutation.mutate("stage");
19345
+ };
19346
+ const unstageFileChanges = (event) => {
19347
+ event?.stopPropagation();
19348
+ stageMutation.mutate("unstage");
19141
19349
  };
19350
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionTrigger$1, {
19351
+ "data-review-file-trigger": true,
19352
+ "data-review-file-value": value,
19353
+ className: "group/review-file-trigger relative flex min-h-10 min-w-0 flex-1 items-center justify-between gap-3 rounded-none border border-transparent bg-background px-3 py-2 text-left text-[13px] font-medium transition-all outline-none hover:no-underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
19354
+ children: [
19355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19356
+ className: "grid min-w-0 flex-1 grid-cols-[auto_auto_minmax(0,1fr)_auto] items-center gap-2",
19357
+ children: [
19358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileStatus, { status: file.status }),
19359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileTypeIcon, { path: file.path }),
19360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19361
+ className: "min-w-0 truncate text-left",
19362
+ children: file.previousPath ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
19363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19364
+ className: "text-muted-foreground",
19365
+ children: file.previousPath
19366
+ }),
19367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19368
+ className: "text-muted-foreground/70",
19369
+ children: " → "
19370
+ }),
19371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: file.path })
19372
+ ] }) : file.path
19373
+ }),
19374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileDiff, { file })
19375
+ ]
19376
+ }),
19377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
19378
+ "data-slot": "accordion-trigger-icon",
19379
+ className: "pointer-events-none size-4 shrink-0 group-aria-expanded/review-file-trigger:hidden"
19380
+ }),
19381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {
19382
+ "data-slot": "accordion-trigger-icon",
19383
+ className: "pointer-events-none hidden size-4 shrink-0 group-aria-expanded/review-file-trigger:inline"
19384
+ })
19385
+ ]
19386
+ });
19142
19387
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionItem, {
19143
19388
  value,
19144
19389
  className: "border-border/70",
19145
19390
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionHeader, {
19146
19391
  className: cn("group/review-file-header sticky top-0 z-20 flex bg-background transition-shadow", stuck && "shadow-sm"),
19147
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionTrigger$1, {
19148
- "data-review-file-trigger": true,
19149
- "data-review-file-value": value,
19150
- className: "group/review-file-trigger relative flex min-h-10 min-w-0 flex-1 items-center justify-between gap-3 rounded-none border border-transparent bg-background px-3 py-2 text-left font-mono text-[13px] font-medium transition-all outline-none hover:no-underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
19392
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenu$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextMenuTrigger, { render: trigger }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuContent, {
19393
+ className: "w-48",
19151
19394
  children: [
19152
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19153
- className: "grid min-w-0 flex-1 grid-cols-[auto_auto_minmax(0,1fr)_auto] items-center gap-2",
19154
- children: [
19155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileStatus, { status: file.status }),
19156
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileTypeIcon, { path: file.path }),
19157
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19158
- className: "min-w-0 truncate text-left",
19159
- children: file.previousPath ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
19160
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19161
- className: "text-muted-foreground",
19162
- children: file.previousPath
19163
- }),
19164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19165
- className: "text-muted-foreground/70",
19166
- children: " → "
19167
- }),
19168
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: file.path })
19169
- ] }) : file.path
19170
- }),
19171
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileDiff, { file })
19172
- ]
19395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19396
+ disabled: !onOpenFile,
19397
+ onClick: openFile,
19398
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowUpRight, {}), "Open file"]
19173
19399
  }),
19174
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
19175
- "data-slot": "accordion-trigger-icon",
19176
- className: "pointer-events-none size-4 shrink-0 group-aria-expanded/review-file-trigger:hidden"
19400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextMenuSeparator, {}),
19401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19402
+ disabled: gitActionDisabled || !canStage,
19403
+ onClick: stageFileChanges,
19404
+ children: [stagePending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "Stage changes"]
19177
19405
  }),
19178
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {
19179
- "data-slot": "accordion-trigger-icon",
19180
- className: "pointer-events-none hidden size-4 shrink-0 group-aria-expanded/review-file-trigger:inline"
19406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19407
+ disabled: gitActionDisabled || !canUnstage,
19408
+ onClick: unstageFileChanges,
19409
+ children: [unstagePending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, {}), "Unstage changes"]
19410
+ }),
19411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19412
+ variant: "destructive",
19413
+ disabled: gitActionDisabled,
19414
+ onClick: discardFileChanges,
19415
+ children: [discardMutation.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, {}), "Discard changes"]
19181
19416
  })
19182
19417
  ]
19183
- })
19418
+ })] })
19184
19419
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionContent, {
19185
19420
  className: "bg-background p-0",
19186
19421
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -19425,7 +19660,7 @@ function assignClassName(element, className) {
19425
19660
  }
19426
19661
  function renderPendingCommitDiffLineComment(pendingComment) {
19427
19662
  const form = document.createElement("form");
19428
- assignClassName(form, "m-2 w-[min(38rem,calc(100vw_-_5rem))] max-w-[calc(100%_-_1rem)] rounded-lg border border-border/70 bg-background p-2 shadow-sm");
19663
+ assignClassName(form, "m-2 w-[calc(100%_-_1rem)] max-w-[38rem] rounded-lg border border-border/70 bg-background p-2 shadow-sm");
19429
19664
  const header = document.createElement("div");
19430
19665
  assignClassName(header, "mb-2 flex min-w-0 items-center justify-between gap-2 text-xs text-muted-foreground");
19431
19666
  const label = document.createElement("span");
@@ -19568,7 +19803,7 @@ function CommitFileDiffAccordionItem({ comments, diffStyle, fileDiff, index, onS
19568
19803
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionTrigger$1, {
19569
19804
  "data-commit-file-trigger": true,
19570
19805
  "data-commit-file-value": value,
19571
- className: "group/commit-file-trigger relative flex min-h-10 min-w-0 flex-1 items-center justify-between gap-3 rounded-none border border-transparent bg-background px-3 py-2 text-left font-mono text-[13px] font-medium transition-all outline-none hover:no-underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
19806
+ className: "group/commit-file-trigger relative flex min-h-10 min-w-0 flex-1 items-center justify-between gap-3 rounded-none border border-transparent bg-background px-3 py-2 text-left text-[13px] font-medium transition-all outline-none hover:no-underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
19572
19807
  children: [
19573
19808
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19574
19809
  className: "grid min-w-0 flex-1 grid-cols-[minmax(0,1fr)_auto] items-baseline gap-3",
@@ -19706,8 +19941,14 @@ function CommitDiffHeaderTitle({ tab }) {
19706
19941
  function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onAddDiffLineComment, tab }) {
19707
19942
  const [diffStyle, setDiffStyle] = import_react.useState("unified");
19708
19943
  const [stickyCommitFileValue, setStickyCommitFileValue] = import_react.useState("");
19944
+ const isMobile = useIsMobile();
19709
19945
  const [commentTarget, setCommentTarget] = import_react.useState(null);
19710
19946
  const diffScrollRef = import_react.useRef(null);
19947
+ const { bottomHeight: diffScrollBottomGradientHeight, onScroll: onDiffScrollGradientScroll, setScrollElement: setDiffScrollGradientElement, topHeight: diffScrollTopGradientHeight } = useScrollGradients();
19948
+ const setDiffScrollElement = import_react.useCallback((element) => {
19949
+ diffScrollRef.current = element;
19950
+ setDiffScrollGradientElement(element);
19951
+ }, [setDiffScrollGradientElement]);
19711
19952
  const updateStickyCommitFileHeader = (scrollElement) => {
19712
19953
  setDerivedScrollState(setStickyCommitFileValue, getStuckScrollTriggerValue({
19713
19954
  getValue: (trigger) => trigger.dataset.commitFileValue || "",
@@ -19773,6 +20014,7 @@ function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onA
19773
20014
  className: "flex min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/70 bg-background p-2",
19774
20015
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffHeaderTitle, { tab }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ToggleGroup$1, {
19775
20016
  variant: "outline",
20017
+ size: isMobile ? "default" : "sm",
19776
20018
  value: [diffStyle],
19777
20019
  onValueChange: (values) => {
19778
20020
  const value = values[0];
@@ -19786,41 +20028,48 @@ function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onA
19786
20028
  children: "Split"
19787
20029
  })]
19788
20030
  })]
19789
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19790
- ref: diffScrollRef,
19791
- className: "min-h-0 flex-1 overflow-auto",
19792
- onScroll: (event) => {
19793
- updateStickyCommitFileHeader(event.currentTarget);
19794
- },
19795
- children: diffQuery.isPending && !diffQuery.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19796
- className: "p-4",
19797
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading commit diff…"] })
19798
- }) : diffQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19799
- className: "p-4",
19800
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19801
- tone: "destructive",
19802
- children: getErrorMessage(diffQuery.error, "Failed to load commit diff")
20031
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20032
+ className: "relative min-h-0 flex-1",
20033
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20034
+ ref: setDiffScrollElement,
20035
+ className: "h-full overflow-auto",
20036
+ onScroll: (event) => {
20037
+ updateStickyCommitFileHeader(event.currentTarget);
20038
+ onDiffScrollGradientScroll(event);
20039
+ },
20040
+ children: diffQuery.isPending && !diffQuery.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20041
+ className: "p-4",
20042
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading commit diff…"] })
20043
+ }) : diffQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20044
+ className: "p-4",
20045
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
20046
+ tone: "destructive",
20047
+ children: getErrorMessage(diffQuery.error, "Failed to load commit diff")
20048
+ })
20049
+ }) : diffQuery.data?.patch && tab.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitSingleFileDiff, {
20050
+ comments: commentsForTab.filter((comment) => {
20051
+ if (!tab.path) return true;
20052
+ return comment.path === tab.path || comment.path === tab.previousPath;
20053
+ }),
20054
+ diffStyle,
20055
+ onStartComment: startLineComment,
20056
+ patch: diffQuery.data.patch,
20057
+ pendingComment
20058
+ }) : diffQuery.data?.patch ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitPatchDiffs, {
20059
+ comments: commentsForTab,
20060
+ diffStyle,
20061
+ onStartComment: startLineComment,
20062
+ patch: diffQuery.data.patch,
20063
+ pendingComment,
20064
+ stuckFileValue: stickyCommitFileValue
20065
+ }, `${diffQuery.data.commit}:${diffQuery.data.mode}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20066
+ className: "p-4",
20067
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, { children: "No line changes." })
19803
20068
  })
19804
- }) : diffQuery.data?.patch && tab.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitSingleFileDiff, {
19805
- comments: commentsForTab.filter((comment) => {
19806
- if (!tab.path) return true;
19807
- return comment.path === tab.path || comment.path === tab.previousPath;
19808
- }),
19809
- diffStyle,
19810
- onStartComment: startLineComment,
19811
- patch: diffQuery.data.patch,
19812
- pendingComment
19813
- }) : diffQuery.data?.patch ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitPatchDiffs, {
19814
- comments: commentsForTab,
19815
- diffStyle,
19816
- onStartComment: startLineComment,
19817
- patch: diffQuery.data.patch,
19818
- pendingComment,
19819
- stuckFileValue: stickyCommitFileValue
19820
- }, `${diffQuery.data.commit}:${diffQuery.data.mode}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19821
- className: "p-4",
19822
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, { children: "No line changes." })
19823
- })
20069
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
20070
+ bottomHeight: diffScrollBottomGradientHeight,
20071
+ topHeight: diffScrollTopGradientHeight
20072
+ })]
19824
20073
  })]
19825
20074
  });
19826
20075
  }
@@ -20053,28 +20302,723 @@ function RightSidebar({ viewerContextId, cwd, active, activeFilePath = "", activ
20053
20302
  })
20054
20303
  }) : null]
20055
20304
  })]
20056
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFilesWorkspace, {
20057
- viewerContextId,
20058
- cwd: normalizedCwd,
20059
- active: active && activeTab === "files",
20060
- activeFilePath: currentFilePath,
20061
- onOpenFile: openFile,
20062
- previewMode
20063
- })
20064
- }),
20065
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectOpenFileDialog, {
20066
- open: localState.openFileDialogOpen,
20067
- onOpenChange: (open) => dispatchLocal({
20068
- type: "set-open-file-dialog-open",
20069
- open
20305
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFilesWorkspace, {
20306
+ viewerContextId,
20307
+ cwd: normalizedCwd,
20308
+ active: active && activeTab === "files",
20309
+ activeFilePath: currentFilePath,
20310
+ onOpenFile: openFile,
20311
+ previewMode
20312
+ })
20313
+ }),
20314
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectOpenFileDialog, {
20315
+ open: localState.openFileDialogOpen,
20316
+ onOpenChange: (open) => dispatchLocal({
20317
+ type: "set-open-file-dialog-open",
20318
+ open
20319
+ }),
20320
+ paths: fileDialogPaths,
20321
+ onOpenFile: (path) => {
20322
+ openFile(path, { pin: true });
20323
+ }
20324
+ })
20325
+ ]
20326
+ })]
20327
+ });
20328
+ }
20329
+ var TERMINAL_ANSI_THEMES = {
20330
+ dark: {
20331
+ black: "#484f58",
20332
+ red: "#ff7b72",
20333
+ green: "#3fb950",
20334
+ yellow: "#d29922",
20335
+ blue: "#58a6ff",
20336
+ magenta: "#bc8cff",
20337
+ cyan: "#39c5cf",
20338
+ white: "#b1bac4",
20339
+ brightBlack: "#6e7681",
20340
+ brightRed: "#ffa198",
20341
+ brightGreen: "#56d364",
20342
+ brightYellow: "#e3b341",
20343
+ brightBlue: "#79c0ff",
20344
+ brightMagenta: "#d2a8ff",
20345
+ brightCyan: "#56d4dd",
20346
+ brightWhite: "#f0f6fc"
20347
+ },
20348
+ light: {
20349
+ black: "#24292f",
20350
+ red: "#cf222e",
20351
+ green: "#116329",
20352
+ yellow: "#953800",
20353
+ blue: "#0969da",
20354
+ magenta: "#8250df",
20355
+ cyan: "#1b7c83",
20356
+ white: "#6e7781",
20357
+ brightBlack: "#57606a",
20358
+ brightRed: "#a40e26",
20359
+ brightGreen: "#1a7f37",
20360
+ brightYellow: "#9a6700",
20361
+ brightBlue: "#218bff",
20362
+ brightMagenta: "#a475f9",
20363
+ brightCyan: "#3192aa",
20364
+ brightWhite: "#24292f"
20365
+ }
20366
+ };
20367
+ var terminalTabCounter = 0;
20368
+ function createTerminalTabId() {
20369
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `terminal-tab-${crypto.randomUUID()}`;
20370
+ terminalTabCounter += 1;
20371
+ return `terminal-tab-${Date.now()}-${terminalTabCounter}`;
20372
+ }
20373
+ function createTerminalTab(id = createTerminalTabId()) {
20374
+ return {
20375
+ id,
20376
+ status: { type: "idle" },
20377
+ terminalId: null
20378
+ };
20379
+ }
20380
+ function createTerminalTabsState(scopeKey) {
20381
+ const storedTabs = readStoredTerminalTabs(scopeKey);
20382
+ const tabs = storedTabs.tabs.map((id) => createTerminalTab(id));
20383
+ return {
20384
+ activeTabId: storedTabs.activeTabId ?? tabs[0]?.id ?? null,
20385
+ scopeKey,
20386
+ tabs
20387
+ };
20388
+ }
20389
+ function rememberTerminalTabsState(state) {
20390
+ rememberStoredTerminalTabs(state.scopeKey, {
20391
+ activeTabId: state.activeTabId,
20392
+ tabs: state.tabs.map((tab) => tab.id)
20393
+ });
20394
+ }
20395
+ function terminalStatusText(status) {
20396
+ switch (status.type) {
20397
+ case "idle": return "Idle";
20398
+ case "connecting": return "Connecting…";
20399
+ case "connected": return status.shell ? `Running ${status.shell}` : "Connected";
20400
+ case "exited": return `Exited ${status.exitCode}`;
20401
+ case "error": return "Terminal error";
20402
+ }
20403
+ }
20404
+ function terminalStatusDetail(status) {
20405
+ switch (status.type) {
20406
+ case "idle": return "This terminal has not started yet.";
20407
+ case "connecting": return "Pico is connecting to the terminal process.";
20408
+ case "connected": return status.shell ? `The terminal is connected and running ${status.shell}.` : "The terminal is connected.";
20409
+ case "exited": return typeof status.signal === "number" ? `The terminal process exited with code ${status.exitCode} and signal ${status.signal}.` : `The terminal process exited with code ${status.exitCode}.`;
20410
+ case "error": return status.message;
20411
+ }
20412
+ }
20413
+ function terminalTabLabel(tab, index) {
20414
+ if (tab.status.type === "connected" && tab.status.shell) return `${index + 1}: ${tab.status.shell}`;
20415
+ return `Terminal ${index + 1}`;
20416
+ }
20417
+ function statusDotClassName(status) {
20418
+ switch (status.type) {
20419
+ case "connected": return "bg-success";
20420
+ case "connecting": return "bg-warning";
20421
+ case "error":
20422
+ case "exited": return "bg-destructive";
20423
+ case "idle": return "bg-muted-foreground/50";
20424
+ }
20425
+ }
20426
+ function cssVariable(name, fallback) {
20427
+ return window.getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
20428
+ }
20429
+ function resolveTerminalThemeMode() {
20430
+ for (const className of document.documentElement.classList) {
20431
+ const mode = appliedThemeClassColorMode(className);
20432
+ if (mode) return mode;
20433
+ }
20434
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
20435
+ }
20436
+ function terminalAnsiTheme() {
20437
+ const fallback = TERMINAL_ANSI_THEMES[resolveTerminalThemeMode()];
20438
+ return {
20439
+ black: cssVariable("--terminal-ansi-black", fallback.black),
20440
+ red: cssVariable("--terminal-ansi-red", fallback.red),
20441
+ green: cssVariable("--terminal-ansi-green", fallback.green),
20442
+ yellow: cssVariable("--terminal-ansi-yellow", fallback.yellow),
20443
+ blue: cssVariable("--terminal-ansi-blue", fallback.blue),
20444
+ magenta: cssVariable("--terminal-ansi-magenta", fallback.magenta),
20445
+ cyan: cssVariable("--terminal-ansi-cyan", fallback.cyan),
20446
+ white: cssVariable("--terminal-ansi-white", fallback.white),
20447
+ brightBlack: cssVariable("--terminal-ansi-bright-black", fallback.brightBlack),
20448
+ brightRed: cssVariable("--terminal-ansi-bright-red", fallback.brightRed),
20449
+ brightGreen: cssVariable("--terminal-ansi-bright-green", fallback.brightGreen),
20450
+ brightYellow: cssVariable("--terminal-ansi-bright-yellow", fallback.brightYellow),
20451
+ brightBlue: cssVariable("--terminal-ansi-bright-blue", fallback.brightBlue),
20452
+ brightMagenta: cssVariable("--terminal-ansi-bright-magenta", fallback.brightMagenta),
20453
+ brightCyan: cssVariable("--terminal-ansi-bright-cyan", fallback.brightCyan),
20454
+ brightWhite: cssVariable("--terminal-ansi-bright-white", fallback.brightWhite)
20455
+ };
20456
+ }
20457
+ function createTerminalTheme() {
20458
+ const ansiTheme = terminalAnsiTheme();
20459
+ return {
20460
+ ...ansiTheme,
20461
+ background: cssVariable("--terminal-background", cssVariable("--background", "#111111")),
20462
+ cursor: cssVariable("--terminal-cursor", cssVariable("--primary", "#7aa2f7")),
20463
+ foreground: cssVariable("--terminal-foreground", cssVariable("--foreground", ansiTheme.brightWhite)),
20464
+ selectionBackground: cssVariable("--terminal-selection-background", cssVariable("--code-selection", "#334155"))
20465
+ };
20466
+ }
20467
+ function createTerminalFontFamily() {
20468
+ return cssVariable("--font-mono", "Menlo, ui-monospace, SFMono-Regular, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace");
20469
+ }
20470
+ function errorMessage(error) {
20471
+ if (error instanceof Error && error.message.trim()) return error.message;
20472
+ if (typeof error === "string" && error.trim()) return error;
20473
+ return "Unknown terminal error";
20474
+ }
20475
+ function exitedStatus(event) {
20476
+ return typeof event.signal === "number" && event.signal > 0 ? {
20477
+ type: "exited",
20478
+ exitCode: event.exitCode,
20479
+ signal: event.signal
20480
+ } : {
20481
+ type: "exited",
20482
+ exitCode: event.exitCode
20483
+ };
20484
+ }
20485
+ function isTerminalToggleEvent(event) {
20486
+ return event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && (event.key === "`" || event.code === "Backquote");
20487
+ }
20488
+ function TerminalStatusDot({ status }) {
20489
+ if (status.type === "connecting") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
20490
+ size: "xs",
20491
+ className: "shrink-0"
20492
+ });
20493
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20494
+ "aria-hidden": "true",
20495
+ className: cn("size-2 shrink-0 rounded-full", statusDotClassName(status))
20496
+ });
20497
+ }
20498
+ function TerminalStatusDialog({ cwd, index, onOpenChange, open, tab }) {
20499
+ const status = tab?.status ?? { type: "idle" };
20500
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
20501
+ open,
20502
+ onOpenChange,
20503
+ focusPromptOnClose: false,
20504
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
20505
+ className: "max-w-sm",
20506
+ children: [
20507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogTitle, { children: [tab ? terminalTabLabel(tab, index) : "Terminal", " status"] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: terminalStatusDetail(status) })] }),
20508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
20509
+ className: "grid gap-3 text-sm",
20510
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20511
+ className: "grid gap-1",
20512
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
20513
+ className: "text-xs font-medium text-muted-foreground",
20514
+ children: "Status"
20515
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: terminalStatusText(status) })]
20516
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20517
+ className: "grid gap-1",
20518
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
20519
+ className: "text-xs font-medium text-muted-foreground",
20520
+ children: "Working directory"
20521
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
20522
+ className: "break-all",
20523
+ children: cwd
20524
+ })]
20525
+ })]
20526
+ }),
20527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, { showCloseButton: true })
20528
+ ]
20529
+ })
20530
+ });
20531
+ }
20532
+ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, selected, sessionId, tabId, viewerContextId }) {
20533
+ const terminalElementRef = import_react.useRef(null);
20534
+ const terminalRef = import_react.useRef(null);
20535
+ const terminalIdRef = import_react.useRef(null);
20536
+ const activeRef = import_react.useRef(active);
20537
+ const fitAndResizeRef = import_react.useRef(() => {});
20538
+ const onStatusChangeRef = import_react.useRef(onStatusChange);
20539
+ const onTerminalIdChangeRef = import_react.useRef(onTerminalIdChange);
20540
+ const [status, setStatus] = import_react.useState({ type: "idle" });
20541
+ import_react.useLayoutEffect(() => {
20542
+ activeRef.current = active;
20543
+ }, [active]);
20544
+ import_react.useEffect(() => {
20545
+ onStatusChangeRef.current = onStatusChange;
20546
+ }, [onStatusChange]);
20547
+ import_react.useEffect(() => {
20548
+ onTerminalIdChangeRef.current = onTerminalIdChange;
20549
+ }, [onTerminalIdChange]);
20550
+ import_react.useEffect(() => {
20551
+ if (!cwd) {
20552
+ setStatus({ type: "idle" });
20553
+ onStatusChangeRef.current(tabId, { type: "idle" });
20554
+ return;
20555
+ }
20556
+ const terminalElement = terminalElementRef.current;
20557
+ if (!terminalElement) return;
20558
+ let disposed = false;
20559
+ let eventSource = null;
20560
+ let inputDisposable = null;
20561
+ let fitAddon = null;
20562
+ let resizeObserver = null;
20563
+ let inputFlushTimer = null;
20564
+ let resizeFrame = null;
20565
+ let inputBuffer = "";
20566
+ let lastResizeKey = "";
20567
+ const requestOptions = sessionId ? {
20568
+ contextId: viewerContextId,
20569
+ sessionId
20570
+ } : { contextId: viewerContextId };
20571
+ const publishStatus = (nextStatus) => {
20572
+ if (disposed) return;
20573
+ setStatus(nextStatus);
20574
+ onStatusChangeRef.current(tabId, nextStatus);
20575
+ };
20576
+ const postJson = async (path, body) => {
20577
+ return await fetchJson(buildRequestUrl(path, requestOptions), {
20578
+ body: JSON.stringify(body),
20579
+ headers: { "content-type": "application/json" },
20580
+ method: "POST"
20581
+ });
20582
+ };
20583
+ const flushInput = () => {
20584
+ inputFlushTimer = null;
20585
+ const id = terminalIdRef.current;
20586
+ if (!id || !inputBuffer) return;
20587
+ const data = inputBuffer;
20588
+ inputBuffer = "";
20589
+ postJson(`/api/terminal/${encodeURIComponent(id)}/input`, { data }).catch((error) => {
20590
+ if (!disposed) publishStatus({
20591
+ type: "error",
20592
+ message: errorMessage(error)
20593
+ });
20594
+ });
20595
+ };
20596
+ const scheduleInputFlush = () => {
20597
+ if (inputFlushTimer !== null) return;
20598
+ inputFlushTimer = window.setTimeout(flushInput, 12);
20599
+ };
20600
+ const fitAndResize = () => {
20601
+ const terminal = terminalRef.current;
20602
+ if (!terminal || !fitAddon) return;
20603
+ try {
20604
+ fitAddon.fit();
20605
+ } catch {
20606
+ return;
20607
+ }
20608
+ const id = terminalIdRef.current;
20609
+ if (!id) return;
20610
+ const resizeKey = `${terminal.cols}x${terminal.rows}`;
20611
+ if (resizeKey === lastResizeKey) return;
20612
+ lastResizeKey = resizeKey;
20613
+ postJson(`/api/terminal/${encodeURIComponent(id)}/resize`, {
20614
+ cols: terminal.cols,
20615
+ rows: terminal.rows
20616
+ }).catch(() => {});
20617
+ };
20618
+ const scheduleFitAndResize = () => {
20619
+ if (resizeFrame !== null) return;
20620
+ resizeFrame = window.requestAnimationFrame(() => {
20621
+ resizeFrame = null;
20622
+ fitAndResize();
20623
+ });
20624
+ };
20625
+ fitAndResizeRef.current = scheduleFitAndResize;
20626
+ const startTerminal = async () => {
20627
+ try {
20628
+ publishStatus({ type: "connecting" });
20629
+ terminalElement.textContent = "";
20630
+ const [{ Terminal }, { FitAddon }] = await Promise.all([import("../_libs/xterm__xterm.mjs").then((n) => n.t), import("../_libs/xterm__addon-fit.mjs").then((n) => n.t)]);
20631
+ if (disposed) return;
20632
+ const terminal = new Terminal({
20633
+ allowTransparency: true,
20634
+ convertEol: false,
20635
+ cursorBlink: true,
20636
+ fontFamily: createTerminalFontFamily(),
20637
+ fontSize: 12,
20638
+ letterSpacing: 0,
20639
+ lineHeight: 1.2,
20640
+ scrollback: 5e3,
20641
+ theme: createTerminalTheme()
20642
+ });
20643
+ fitAddon = new FitAddon();
20644
+ terminal.loadAddon(fitAddon);
20645
+ terminal.open(terminalElement);
20646
+ terminalRef.current = terminal;
20647
+ inputDisposable = terminal.onData((data) => {
20648
+ if (!activeRef.current) return;
20649
+ inputBuffer += data;
20650
+ scheduleInputFlush();
20651
+ });
20652
+ scheduleFitAndResize();
20653
+ const terminalResponse = await postJson("/api/terminal", {
20654
+ clientKey: tabId,
20655
+ cols: terminal.cols,
20656
+ rows: terminal.rows
20657
+ });
20658
+ if (disposed) return;
20659
+ terminalIdRef.current = terminalResponse.id;
20660
+ onTerminalIdChangeRef.current(tabId, terminalResponse.id);
20661
+ publishStatus({
20662
+ type: "connected",
20663
+ shell: terminalResponse.shell
20664
+ });
20665
+ eventSource = new EventSource(buildRequestUrl(`/api/terminal/${encodeURIComponent(terminalResponse.id)}/events`, requestOptions));
20666
+ eventSource.onmessage = (message) => {
20667
+ if (disposed) return;
20668
+ const event = JSON.parse(message.data);
20669
+ if (event.type === "output") {
20670
+ terminal.write(event.data);
20671
+ return;
20672
+ }
20673
+ if (event.type === "ready") {
20674
+ publishStatus({
20675
+ type: "connected",
20676
+ shell: event.shell
20677
+ });
20678
+ return;
20679
+ }
20680
+ if (event.type === "exit") {
20681
+ publishStatus(exitedStatus(event));
20682
+ return;
20683
+ }
20684
+ if (event.type === "error") publishStatus({
20685
+ type: "error",
20686
+ message: event.error
20687
+ });
20688
+ };
20689
+ eventSource.onerror = () => {
20690
+ if (!disposed && terminalIdRef.current) setStatus((current) => {
20691
+ if (current.type === "connected") return current;
20692
+ const nextStatus = {
20693
+ type: "error",
20694
+ message: "Terminal connection interrupted."
20695
+ };
20696
+ onStatusChangeRef.current(tabId, nextStatus);
20697
+ return nextStatus;
20698
+ });
20699
+ };
20700
+ resizeObserver = new ResizeObserver(scheduleFitAndResize);
20701
+ resizeObserver.observe(terminalElement);
20702
+ scheduleFitAndResize();
20703
+ } catch (error) {
20704
+ if (disposed) return;
20705
+ const message = errorMessage(error);
20706
+ publishStatus({
20707
+ type: "error",
20708
+ message
20709
+ });
20710
+ terminalRef.current?.writeln(`\r\n${message}`);
20711
+ }
20712
+ };
20713
+ startTerminal();
20714
+ return () => {
20715
+ disposed = true;
20716
+ if (inputFlushTimer !== null) window.clearTimeout(inputFlushTimer);
20717
+ if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
20718
+ flushInput();
20719
+ resizeObserver?.disconnect();
20720
+ eventSource?.close();
20721
+ inputDisposable?.dispose();
20722
+ terminalRef.current?.dispose();
20723
+ terminalRef.current = null;
20724
+ terminalIdRef.current = null;
20725
+ fitAndResizeRef.current = () => {};
20726
+ };
20727
+ }, [
20728
+ cwd,
20729
+ sessionId,
20730
+ tabId,
20731
+ viewerContextId
20732
+ ]);
20733
+ import_react.useEffect(() => {
20734
+ const applyTheme = () => {
20735
+ const terminal = terminalRef.current;
20736
+ if (!terminal) return;
20737
+ terminal.options.theme = createTerminalTheme();
20738
+ terminal.options.fontFamily = createTerminalFontFamily();
20739
+ };
20740
+ const observer = new MutationObserver(applyTheme);
20741
+ observer.observe(document.documentElement, {
20742
+ attributeFilter: ["class", "data-pico-theme-mode"],
20743
+ attributes: true
20744
+ });
20745
+ applyTheme();
20746
+ return () => observer.disconnect();
20747
+ }, []);
20748
+ import_react.useEffect(() => {
20749
+ if (!active) {
20750
+ terminalRef.current?.blur();
20751
+ return;
20752
+ }
20753
+ const frame = window.requestAnimationFrame(() => {
20754
+ fitAndResizeRef.current();
20755
+ terminalRef.current?.focus();
20756
+ });
20757
+ return () => window.cancelAnimationFrame(frame);
20758
+ }, [active, status]);
20759
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20760
+ "aria-hidden": !selected,
20761
+ "data-terminal-tab": tabId,
20762
+ className: cn("absolute inset-0 min-h-0 w-full overflow-hidden bg-background", selected ? "z-10 opacity-100" : "pointer-events-none z-0 opacity-0"),
20763
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20764
+ ref: terminalElementRef,
20765
+ className: "h-full min-h-0 w-full"
20766
+ })
20767
+ });
20768
+ }
20769
+ function TerminalPanel({ active, className, cwd, onClose, sessionId, showCloseButton = true, viewerContextId }) {
20770
+ const normalizedCwd = cwd?.trim() || "";
20771
+ const scopeKey = `${viewerContextId}:${sessionId ?? "no-session"}:${normalizedCwd}`;
20772
+ const [tabsState, setTabsState] = import_react.useState(() => createTerminalTabsState(scopeKey));
20773
+ const [statusDialogTabId, setStatusDialogTabId] = import_react.useState(null);
20774
+ const tabs = tabsState.scopeKey === scopeKey ? tabsState.tabs : [];
20775
+ const selectedTabId = tabsState.scopeKey === scopeKey ? tabsState.activeTabId ?? tabs[0]?.id ?? null : null;
20776
+ import_react.useEffect(() => {
20777
+ if (tabsState.scopeKey === scopeKey) return;
20778
+ setTabsState(createTerminalTabsState(scopeKey));
20779
+ }, [scopeKey, tabsState.scopeKey]);
20780
+ import_react.useEffect(() => {
20781
+ if (!active || !normalizedCwd || tabs.length > 0 || tabsState.scopeKey !== scopeKey) return;
20782
+ const tab = createTerminalTab();
20783
+ const nextState = {
20784
+ activeTabId: tab.id,
20785
+ scopeKey,
20786
+ tabs: [tab]
20787
+ };
20788
+ rememberTerminalTabsState(nextState);
20789
+ setTabsState(nextState);
20790
+ }, [
20791
+ active,
20792
+ normalizedCwd,
20793
+ scopeKey,
20794
+ tabs.length,
20795
+ tabsState.scopeKey
20796
+ ]);
20797
+ const activeStatus = tabs.find((tab) => tab.id === selectedTabId)?.status ?? { type: "idle" };
20798
+ const activeError = activeStatus.type === "error" ? activeStatus.message : void 0;
20799
+ const statusDialogTab = tabs.find((tab) => tab.id === statusDialogTabId);
20800
+ const statusDialogTabIndex = statusDialogTab ? tabs.indexOf(statusDialogTab) : 0;
20801
+ const requestOptions = sessionId ? {
20802
+ contextId: viewerContextId,
20803
+ sessionId
20804
+ } : { contextId: viewerContextId };
20805
+ const addTerminalTab = () => {
20806
+ if (!normalizedCwd) return;
20807
+ const tab = createTerminalTab();
20808
+ setTabsState((current) => {
20809
+ const nextState = current.scopeKey !== scopeKey ? {
20810
+ activeTabId: tab.id,
20811
+ scopeKey,
20812
+ tabs: [tab]
20813
+ } : {
20814
+ ...current,
20815
+ activeTabId: tab.id,
20816
+ tabs: [...current.tabs, tab]
20817
+ };
20818
+ rememberTerminalTabsState(nextState);
20819
+ return nextState;
20820
+ });
20821
+ };
20822
+ const closeServerTerminal = (terminalId) => {
20823
+ if (!terminalId) return;
20824
+ fetchJson(buildRequestUrl(`/api/terminal/${encodeURIComponent(terminalId)}/close`, requestOptions), { method: "POST" }).catch(() => {});
20825
+ };
20826
+ const closeTerminalTab = (tabId) => {
20827
+ if (tabs.length <= 0) return;
20828
+ closeServerTerminal(tabs.find((tab) => tab.id === tabId)?.terminalId ?? null);
20829
+ setTabsState((current) => {
20830
+ if (current.scopeKey !== scopeKey || current.tabs.length <= 0) return current;
20831
+ const closingIndex = current.tabs.findIndex((tab) => tab.id === tabId);
20832
+ const nextTabs = current.tabs.filter((tab) => tab.id !== tabId);
20833
+ const nextActiveTabId = current.activeTabId === tabId ? nextTabs[Math.min(Math.max(closingIndex, 0), nextTabs.length - 1)]?.id ?? null : current.activeTabId;
20834
+ const nextState = {
20835
+ ...current,
20836
+ activeTabId: nextActiveTabId,
20837
+ tabs: nextTabs
20838
+ };
20839
+ rememberTerminalTabsState(nextState);
20840
+ return nextState;
20841
+ });
20842
+ };
20843
+ const updateTabStatus = (tabId, status) => {
20844
+ setTabsState((current) => {
20845
+ if (current.scopeKey !== scopeKey) return current;
20846
+ return {
20847
+ ...current,
20848
+ tabs: current.tabs.map((tab) => tab.id === tabId ? {
20849
+ ...tab,
20850
+ status
20851
+ } : tab)
20852
+ };
20853
+ });
20854
+ };
20855
+ const updateTabTerminalId = (tabId, terminalId) => {
20856
+ setTabsState((current) => {
20857
+ if (current.scopeKey !== scopeKey) return current;
20858
+ return {
20859
+ ...current,
20860
+ tabs: current.tabs.map((tab) => tab.id === tabId ? {
20861
+ ...tab,
20862
+ terminalId
20863
+ } : tab)
20864
+ };
20865
+ });
20866
+ };
20867
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
20868
+ "data-terminal-panel": "true",
20869
+ className: cn("pico-terminal-panel flex h-full min-h-0 w-full min-w-0 flex-col bg-background", className),
20870
+ onKeyDownCapture: (event) => {
20871
+ if (!isTerminalToggleEvent(event) || !onClose) return;
20872
+ event.preventDefault();
20873
+ event.stopPropagation();
20874
+ event.nativeEvent.stopImmediatePropagation();
20875
+ onClose();
20876
+ },
20877
+ children: [
20878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20879
+ className: "flex min-h-10 shrink-0 items-center gap-2 border-b border-border/70 px-2",
20880
+ children: [
20881
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20882
+ className: "hidden max-w-56 min-w-0 flex-col gap-0 px-1 sm:flex",
20883
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20884
+ className: "truncate text-xs font-medium",
20885
+ children: "Terminal"
20886
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20887
+ className: "flex min-w-0 items-center gap-1 truncate text-[11px] text-muted-foreground",
20888
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalStatusDot, { status: activeStatus }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20889
+ className: "truncate",
20890
+ children: normalizedCwd ? formatDisplayPath$1(normalizedCwd) : "No cwd"
20891
+ })]
20892
+ })]
20070
20893
  }),
20071
- paths: fileDialogPaths,
20072
- onOpenFile: (path) => {
20073
- openFile(path, { pin: true });
20074
- }
20894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20895
+ className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto py-1",
20896
+ children: [tabs.map((tab, index) => {
20897
+ const selected = tab.id === selectedTabId;
20898
+ const label = terminalTabLabel(tab, index);
20899
+ const statusText = terminalStatusText(tab.status);
20900
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20901
+ className: cn("group flex h-7 max-w-48 shrink-0 items-center overflow-hidden rounded-md border text-xs", selected ? "border-border bg-muted text-foreground" : "border-transparent bg-transparent text-muted-foreground hover:bg-muted/60 hover:text-foreground"),
20902
+ children: [
20903
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20904
+ title: `${label} · ${statusText}`,
20905
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
20906
+ type: "button",
20907
+ className: "flex h-full w-7 shrink-0 items-center justify-center outline-hidden focus-visible:ring-1 focus-visible:ring-ring",
20908
+ "aria-label": `Show ${label} status`,
20909
+ onClick: (event) => {
20910
+ event.stopPropagation();
20911
+ setStatusDialogTabId(tab.id);
20912
+ },
20913
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalStatusDot, { status: tab.status })
20914
+ })
20915
+ }),
20916
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20917
+ title: `${label} · ${statusText}`,
20918
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
20919
+ type: "button",
20920
+ className: "flex h-full min-w-0 flex-1 items-center px-1 text-left outline-hidden focus-visible:ring-1 focus-visible:ring-ring",
20921
+ onClick: () => {
20922
+ setTabsState((current) => {
20923
+ if (current.scopeKey !== scopeKey) return current;
20924
+ const nextState = {
20925
+ ...current,
20926
+ activeTabId: tab.id
20927
+ };
20928
+ rememberTerminalTabsState(nextState);
20929
+ return nextState;
20930
+ });
20931
+ },
20932
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20933
+ className: "truncate",
20934
+ children: label
20935
+ })
20936
+ })
20937
+ }),
20938
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20939
+ title: `Close ${label}`,
20940
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
20941
+ type: "button",
20942
+ className: "flex h-full w-6 shrink-0 items-center justify-center text-muted-foreground opacity-70 outline-hidden group-hover:opacity-100 hover:bg-muted hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-30",
20943
+ "aria-label": `Close ${label}`,
20944
+ onClick: (event) => {
20945
+ event.stopPropagation();
20946
+ closeTerminalTab(tab.id);
20947
+ },
20948
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3" })
20949
+ })
20950
+ })
20951
+ ]
20952
+ }, tab.id);
20953
+ }), normalizedCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20954
+ title: "New terminal tab",
20955
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
20956
+ size: "icon",
20957
+ variant: "ghost",
20958
+ className: "size-7 shrink-0",
20959
+ "aria-label": "New terminal tab",
20960
+ onClick: addTerminalTab,
20961
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "size-4" })
20962
+ })
20963
+ }) : null]
20964
+ }),
20965
+ activeError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20966
+ title: activeError,
20967
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20968
+ className: "hidden max-w-48 truncate text-[11px] text-destructive sm:inline",
20969
+ children: activeError
20970
+ })
20971
+ }) : null,
20972
+ onClose && showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20973
+ title: "Hide terminal",
20974
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
20975
+ size: "icon",
20976
+ variant: "ghost",
20977
+ className: "size-8 shrink-0",
20978
+ "aria-label": "Hide terminal",
20979
+ onClick: onClose,
20980
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {})
20981
+ })
20982
+ }) : null
20983
+ ]
20984
+ }),
20985
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalStatusDialog, {
20986
+ cwd: normalizedCwd,
20987
+ index: statusDialogTabIndex,
20988
+ open: statusDialogTab !== void 0,
20989
+ tab: statusDialogTab,
20990
+ onOpenChange: (open) => {
20991
+ if (!open) setStatusDialogTabId(null);
20992
+ }
20993
+ }),
20994
+ normalizedCwd ? tabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20995
+ className: "relative min-h-0 flex-1 overflow-hidden bg-background",
20996
+ children: tabs.map((tab) => {
20997
+ const selected = tab.id === selectedTabId;
20998
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalTabPane, {
20999
+ active: active && selected,
21000
+ cwd: normalizedCwd,
21001
+ onStatusChange: updateTabStatus,
21002
+ onTerminalIdChange: updateTabTerminalId,
21003
+ selected,
21004
+ sessionId,
21005
+ tabId: tab.id,
21006
+ viewerContextId
21007
+ }, tab.id);
20075
21008
  })
20076
- ]
20077
- })]
21009
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21010
+ className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-sm text-muted-foreground",
21011
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "No terminal tabs are open." }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
21012
+ size: "sm",
21013
+ variant: "outline",
21014
+ onClick: addTerminalTab,
21015
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "size-4" }), "New terminal tab"]
21016
+ })]
21017
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21018
+ className: "flex min-h-0 flex-1 items-center justify-center p-6 text-sm text-muted-foreground",
21019
+ children: "Open a session before starting a terminal."
21020
+ })
21021
+ ]
20078
21022
  });
20079
21023
  }
20080
21024
  function readStoredRightSidebarActiveTab() {
@@ -20191,22 +21135,33 @@ function reorderRightSidebarFiles(store, paths) {
20191
21135
  fileTabs: uniquePaths
20192
21136
  }));
20193
21137
  }
21138
+ function shallowTerminalSession(left, right) {
21139
+ return left.cwd === right.cwd && left.sessionId === right.sessionId;
21140
+ }
20194
21141
  function AppShellTabsList({ sessionStore, viewerContextId }) {
20195
21142
  const cwd = useSelector(sessionStore, (sessionState) => sessionState.cwd);
20196
21143
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsList, {
20197
21144
  className: "w-full gap-2 rounded-none border-b border-border/70 bg-background p-2 group-data-horizontal/tabs:h-auto md:hidden",
20198
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
20199
- value: "session",
20200
- className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
20201
- children: "Session"
20202
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
20203
- value: "git",
20204
- className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
20205
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitTabStatusText, {
20206
- viewerContextId,
20207
- cwd
21145
+ children: [
21146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
21147
+ value: "session",
21148
+ className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
21149
+ children: "Session"
21150
+ }),
21151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
21152
+ value: "git",
21153
+ className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
21154
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitTabStatusText, {
21155
+ viewerContextId,
21156
+ cwd
21157
+ })
21158
+ }),
21159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
21160
+ value: "terminal",
21161
+ className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
21162
+ children: "Terminal"
20208
21163
  })
20209
- })]
21164
+ ]
20210
21165
  });
20211
21166
  }
20212
21167
  var AppShellGitPanelController = import_react.memo(function AppShellGitPanelController({ active, composerDiffLineComments, onAddDiffLineComment, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onOpenFile, onReorderFiles, rightSidebarStore, sessionStore, viewerContextId }) {
@@ -20285,6 +21240,10 @@ var DESKTOP_DEFAULT_GIT_PANEL_WIDTH = 320;
20285
21240
  var DESKTOP_MIN_SESSION_WIDTH = 320;
20286
21241
  var DESKTOP_MIN_SIDE_PANEL_WIDTH = 260;
20287
21242
  var DESKTOP_MAX_STORED_SIDE_PANEL_WIDTH = 1600;
21243
+ var DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT = 280;
21244
+ var DESKTOP_MIN_TERMINAL_PANEL_HEIGHT = 160;
21245
+ var DESKTOP_MIN_TOP_PANEL_HEIGHT = 220;
21246
+ var DESKTOP_MAX_STORED_TERMINAL_PANEL_HEIGHT = 1200;
20288
21247
  function defaultDesktopSidePanelWidths() {
20289
21248
  return {
20290
21249
  fileViewWidth: DESKTOP_DEFAULT_FILE_VIEW_WIDTH,
@@ -20319,6 +21278,18 @@ function storeDesktopSidePanelWidths(widths) {
20319
21278
  function sameDesktopSidePanelWidths(left, right) {
20320
21279
  return left.fileViewWidth === right.fileViewWidth && left.gitPanelWidth === right.gitPanelWidth;
20321
21280
  }
21281
+ function clampStoredDesktopTerminalPanelHeight(value) {
21282
+ if (value == null || value === "") return DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT;
21283
+ const height = typeof value === "number" ? value : Number(value);
21284
+ if (!Number.isFinite(height)) return DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT;
21285
+ return Math.round(Math.min(DESKTOP_MAX_STORED_TERMINAL_PANEL_HEIGHT, Math.max(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, height)));
21286
+ }
21287
+ function readStoredDesktopTerminalPanelHeight() {
21288
+ return clampStoredDesktopTerminalPanelHeight(safeLocalStorageGetItem(TERMINAL_PANEL_HEIGHT_STORAGE_KEY));
21289
+ }
21290
+ function storeDesktopTerminalPanelHeight(height) {
21291
+ safeLocalStorageSetItem(TERMINAL_PANEL_HEIGHT_STORAGE_KEY, String(clampStoredDesktopTerminalPanelHeight(height)));
21292
+ }
20322
21293
  function clampDesktopPanelSize(value, min, max) {
20323
21294
  if (!Number.isFinite(value)) return min;
20324
21295
  if (max <= min) return Math.max(0, max);
@@ -20442,21 +21413,106 @@ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, o
20442
21413
  }
20443
21414
  });
20444
21415
  }
21416
+ function AppShellDesktopVerticalResizeHandle({ label, max, min, onResize, onResizeEnd, onResizeStart, size }) {
21417
+ const propsRef = useLatestRef({
21418
+ max,
21419
+ min,
21420
+ onResize,
21421
+ onResizeEnd,
21422
+ onResizeStart,
21423
+ size
21424
+ });
21425
+ const [verticalResizeCursor, setVerticalResizeCursor] = import_react.useState("row-resize");
21426
+ const [resizeTargetMinimumSize, setResizeTargetMinimumSize] = import_react.useState(10);
21427
+ import_react.useEffect(() => {
21428
+ const updateResizeTarget = () => {
21429
+ setResizeTargetMinimumSize(getSidebarResizeTargetMinimumSize());
21430
+ };
21431
+ const coarsePointerQuery = window.matchMedia("(pointer:coarse)");
21432
+ setVerticalResizeCursor(getSidebarVerticalResizeCursor());
21433
+ updateResizeTarget();
21434
+ coarsePointerQuery.addEventListener("change", updateResizeTarget);
21435
+ return () => {
21436
+ coarsePointerQuery.removeEventListener("change", updateResizeTarget);
21437
+ };
21438
+ }, []);
21439
+ const verticalResizeCursorClass = verticalResizeCursor === "ns-resize" ? "cursor-ns-resize" : "cursor-row-resize";
21440
+ const resizeTo = (nextSize) => {
21441
+ const current = propsRef.current;
21442
+ current.onResize(clampDesktopPanelSize(nextSize, current.min, current.max));
21443
+ };
21444
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21445
+ role: "separator",
21446
+ "aria-label": label,
21447
+ "aria-orientation": "horizontal",
21448
+ tabIndex: 0,
21449
+ style: {
21450
+ "--resize-target-height": `${resizeTargetMinimumSize}px`,
21451
+ cursor: verticalResizeCursor
21452
+ },
21453
+ className: `absolute inset-x-0 top-0 z-20 h-(--resize-target-height) -translate-y-1/2 touch-none bg-transparent outline-hidden after:absolute after:inset-x-0 after:top-1/2 after:h-px after:bg-transparent focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 ${resizeRailPrimaryInteractiveClass} ${verticalResizeCursorClass}`,
21454
+ onKeyDown: (event) => {
21455
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
21456
+ event.preventDefault();
21457
+ const delta = event.shiftKey ? 48 : 16;
21458
+ resizeTo(propsRef.current.size + (event.key === "ArrowUp" ? delta : -delta));
21459
+ },
21460
+ onPointerDown: (event) => {
21461
+ if (event.button !== 0) return;
21462
+ event.preventDefault();
21463
+ const current = propsRef.current;
21464
+ const startY = event.clientY;
21465
+ const startSize = current.size;
21466
+ const cursor = getSidebarVerticalResizeCursor();
21467
+ const previousCursor = document.body.style.cursor;
21468
+ const previousUserSelect = document.body.style.userSelect;
21469
+ const cleanupGlobalResizeCursor = installGlobalResizeCursor(cursor);
21470
+ current.onResizeStart?.();
21471
+ Object.assign(document.body.style, {
21472
+ userSelect: "none",
21473
+ cursor
21474
+ });
21475
+ const handlePointerMove = (moveEvent) => {
21476
+ resizeTo(startSize + startY - moveEvent.clientY);
21477
+ };
21478
+ const handlePointerUp = () => {
21479
+ cleanupGlobalResizeCursor();
21480
+ Object.assign(document.body.style, {
21481
+ userSelect: previousUserSelect,
21482
+ cursor: previousCursor
21483
+ });
21484
+ document.removeEventListener("pointermove", handlePointerMove);
21485
+ document.removeEventListener("pointerup", handlePointerUp);
21486
+ document.removeEventListener("pointercancel", handlePointerUp);
21487
+ propsRef.current.onResizeEnd?.();
21488
+ };
21489
+ document.addEventListener("pointermove", handlePointerMove);
21490
+ document.addEventListener("pointerup", handlePointerUp);
21491
+ document.addEventListener("pointercancel", handlePointerUp);
21492
+ }
21493
+ });
21494
+ }
20445
21495
  function AppShellTabsController(props) {
20446
21496
  return useAppShellTabsControllerView(props);
20447
21497
  }
20448
- function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTurn, composerDiffLineComments, composerPanelRef, contextUsageStore, conversationFrameRef, conversationItemsStore, defaultNewSessionDirectory, displaySettingsStore, fileInputRef, gitPanelOpen, hiddenThinkingPreviewStore, isSessionViewLoading, isSubmitting, isMobile, newSessionDirectoryOptions, onCancelCompaction, onCreateSession, onOpenAddDirectoryDialog, onCloseAllFileViewTabs, onCloseFileViewTab, onCloseFileViewTabsToRight, onCloseOtherFileViewTabs, onAddDiffLineComment, onOpenFileViewTab, onReorderFileViewTabs, onValueChange, rightSidebarStore, sessionStore, store, viewerContextId, workingStateStore }) {
21498
+ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTurn, composerDiffLineComments, composerPanelRef, contextUsageStore, conversationFrameRef, conversationItemsStore, defaultNewSessionDirectory, displaySettingsStore, fileInputRef, gitPanelOpen, terminalPanelOpen, hiddenThinkingPreviewStore, isSessionViewLoading, isSubmitting, isMobile, newSessionDirectoryOptions, onCancelCompaction, onCreateSession, onOpenAddDirectoryDialog, onCloseAllFileViewTabs, onCloseFileViewTab, onCloseFileViewTabsToRight, onCloseOtherFileViewTabs, onCloseTerminalPanel, onAddDiffLineComment, onOpenFileViewTab, onReorderFileViewTabs, onValueChange, rightSidebarStore, sessionStore, store, viewerContextId, workingStateStore }) {
20449
21499
  const currentTab = useSelector(appUiStore, (state) => state.currentTab);
20450
21500
  const isDraftSession = useSelector(sessionStore, (sessionState) => sessionState.draft);
21501
+ const terminalSession = useSelector(sessionStore, (sessionState) => ({
21502
+ cwd: sessionState.cwd,
21503
+ sessionId: sessionState.sessionId
21504
+ }), { compare: shallowTerminalSession });
20451
21505
  const showTabsList = isMobile || !isDraftSession || isSessionViewLoading;
20452
- const sessionVisibleClassName = currentTab === "git" ? "hidden min-h-0 flex-1 flex-col md:flex" : "flex min-h-0 flex-1 flex-col";
21506
+ const sessionVisibleClassName = currentTab === "session" ? "flex min-h-0 flex-1 flex-col" : "hidden min-h-0 flex-1 flex-col md:flex";
20453
21507
  const mobileGitClassName = currentTab === "git" ? "min-h-0 flex-1 overflow-hidden md:hidden" : "hidden";
21508
+ const mobileTerminalClassName = currentTab === "terminal" ? "min-h-0 flex-1 overflow-hidden md:hidden" : "hidden";
20454
21509
  const rightSidebarHasVisibleFiles = useSelector(rightSidebarStore, selectRightSidebarHasVisibleFiles);
20455
21510
  const desktopGitPanelOpen = !isMobile && gitPanelOpen;
20456
21511
  const desktopFileViewOpen = !isMobile && gitPanelOpen && rightSidebarHasVisibleFiles;
20457
21512
  const desktopSideWorkspaceOpen = desktopFileViewOpen || desktopGitPanelOpen;
20458
21513
  const desktopLayoutRef = import_react.useRef(null);
20459
21514
  const [desktopLayoutWidth, setDesktopLayoutWidth] = import_react.useState(0);
21515
+ const [desktopLayoutHeight, setDesktopLayoutHeight] = import_react.useState(0);
20460
21516
  const [desktopSidePanelWidths, setDesktopSidePanelWidthsState] = import_react.useState(() => defaultDesktopSidePanelWidths());
20461
21517
  const desktopSidePanelWidthsLoadedRef = import_react.useRef(false);
20462
21518
  const desktopSidePanelWidthsRef = import_react.useRef(desktopSidePanelWidths);
@@ -20484,12 +21540,30 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20484
21540
  }));
20485
21541
  };
20486
21542
  const [desktopGitPanelMounted, setDesktopGitPanelMounted] = import_react.useState(desktopGitPanelOpen);
21543
+ const [desktopTerminalPanelHeight, setDesktopTerminalPanelHeightState] = import_react.useState(() => DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT);
21544
+ const desktopTerminalPanelHeightLoadedRef = import_react.useRef(false);
21545
+ const desktopTerminalPanelHeightRef = import_react.useRef(desktopTerminalPanelHeight);
21546
+ desktopTerminalPanelHeightRef.current = desktopTerminalPanelHeight;
21547
+ const [desktopTerminalPanelMounted, setDesktopTerminalPanelMounted] = import_react.useState(!isMobile && terminalPanelOpen);
20487
21548
  const [desktopPanelResizing, setDesktopPanelResizing] = import_react.useState(false);
21549
+ const [desktopTerminalResizing, setDesktopTerminalResizing] = import_react.useState(false);
21550
+ const setDesktopTerminalPanelHeight = (action) => {
21551
+ const current = desktopTerminalPanelHeightRef.current;
21552
+ const next = clampStoredDesktopTerminalPanelHeight(applyStoreAction(current, action));
21553
+ if (current === next) return;
21554
+ desktopTerminalPanelHeightRef.current = next;
21555
+ setDesktopTerminalPanelHeightState(next);
21556
+ if (desktopTerminalPanelHeightLoadedRef.current) storeDesktopTerminalPanelHeight(next);
21557
+ };
20488
21558
  import_react.useEffect(() => {
20489
21559
  const storedWidths = readStoredDesktopSidePanelWidths();
21560
+ const storedTerminalPanelHeight = readStoredDesktopTerminalPanelHeight();
20490
21561
  desktopSidePanelWidthsLoadedRef.current = true;
21562
+ desktopTerminalPanelHeightLoadedRef.current = true;
20491
21563
  desktopSidePanelWidthsRef.current = storedWidths;
21564
+ desktopTerminalPanelHeightRef.current = storedTerminalPanelHeight;
20492
21565
  setDesktopSidePanelWidthsState((current) => sameDesktopSidePanelWidths(current, storedWidths) ? current : storedWidths);
21566
+ setDesktopTerminalPanelHeightState((current) => current === storedTerminalPanelHeight ? current : storedTerminalPanelHeight);
20493
21567
  }, []);
20494
21568
  const sessionPane = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20495
21569
  className: "flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
@@ -20517,7 +21591,7 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20517
21591
  viewerContextId,
20518
21592
  workingStateStore
20519
21593
  })
20520
- }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21594
+ }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20521
21595
  className: mobileGitClassName,
20522
21596
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellGitPanelController, {
20523
21597
  viewerContextId,
@@ -20533,32 +21607,50 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20533
21607
  onOpenFile: onOpenFileViewTab,
20534
21608
  onReorderFiles: onReorderFileViewTabs
20535
21609
  })
20536
- }) : null]
21610
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21611
+ className: mobileTerminalClassName,
21612
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalPanel, {
21613
+ active: currentTab === "terminal",
21614
+ cwd: terminalSession.cwd,
21615
+ onClose: onCloseTerminalPanel,
21616
+ sessionId: terminalSession.sessionId,
21617
+ showCloseButton: false,
21618
+ viewerContextId
21619
+ })
21620
+ })] }) : null]
20537
21621
  });
20538
21622
  import_react.useLayoutEffect(() => {
20539
21623
  if (isMobile) {
20540
21624
  setDesktopGitPanelMounted(false);
21625
+ setDesktopTerminalPanelMounted(false);
20541
21626
  return;
20542
21627
  }
20543
21628
  if (desktopSideWorkspaceOpen) setDesktopGitPanelMounted(true);
20544
- }, [desktopSideWorkspaceOpen, isMobile]);
21629
+ if (terminalPanelOpen) setDesktopTerminalPanelMounted(true);
21630
+ }, [
21631
+ desktopSideWorkspaceOpen,
21632
+ isMobile,
21633
+ terminalPanelOpen
21634
+ ]);
20545
21635
  import_react.useLayoutEffect(() => {
20546
21636
  if (isMobile) return;
20547
21637
  const element = desktopLayoutRef.current;
20548
21638
  if (!element) return;
20549
- const updateWidth = () => {
21639
+ const updateSize = () => {
20550
21640
  setDesktopLayoutWidth(element.clientWidth);
21641
+ setDesktopLayoutHeight(element.clientHeight);
20551
21642
  };
20552
- updateWidth();
21643
+ updateSize();
20553
21644
  if (typeof ResizeObserver === "undefined") {
20554
- window.addEventListener("resize", updateWidth);
20555
- return () => window.removeEventListener("resize", updateWidth);
21645
+ window.addEventListener("resize", updateSize);
21646
+ return () => window.removeEventListener("resize", updateSize);
20556
21647
  }
20557
- const observer = new ResizeObserver(updateWidth);
21648
+ const observer = new ResizeObserver(updateSize);
20558
21649
  observer.observe(element);
20559
21650
  return () => observer.disconnect();
20560
21651
  }, [isMobile]);
20561
21652
  const desktopAvailableWidth = desktopLayoutWidth || (typeof window === "undefined" ? 1200 : window.innerWidth);
21653
+ const desktopAvailableHeight = desktopLayoutHeight || (typeof window === "undefined" ? 800 : window.innerHeight);
20562
21654
  const desktopGitPanelRendered = desktopSideWorkspaceOpen || desktopGitPanelMounted || desktopGitPanelOpen;
20563
21655
  const desktopFittedWidths = fitDesktopSidePanelWidths({
20564
21656
  availableWidth: desktopAvailableWidth,
@@ -20569,14 +21661,25 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20569
21661
  });
20570
21662
  const desktopSideWorkspaceRendered = desktopSideWorkspaceOpen || desktopGitPanelMounted;
20571
21663
  const desktopSideWorkspaceWidth = desktopSideWorkspaceOpen ? desktopFittedWidths.sideWidth : 0;
20572
- const desktopTransitionClassName = !desktopPanelResizing ? "transition-[width] duration-200 ease-linear" : "";
20573
- const desktopPanelGroupClassName = "relative flex h-full min-h-0 w-full flex-1 overflow-hidden";
21664
+ const desktopTerminalOpen = !isMobile && terminalPanelOpen;
21665
+ const desktopTerminalRendered = desktopTerminalOpen || desktopTerminalPanelMounted;
21666
+ const desktopTerminalMaxHeight = Math.max(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopAvailableHeight - DESKTOP_MIN_TOP_PANEL_HEIGHT);
21667
+ const desktopFittedTerminalPanelHeight = clampDesktopPanelSize(desktopTerminalPanelHeight, Math.min(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopTerminalMaxHeight), desktopTerminalMaxHeight);
21668
+ const desktopTerminalVisibleHeight = desktopTerminalOpen ? desktopFittedTerminalPanelHeight : 0;
21669
+ const desktopTransitionClassName = !desktopPanelResizing && !desktopTerminalResizing ? "transition-[width,height] duration-200 ease-linear" : "";
21670
+ const desktopPanelGroupClassName = "relative flex min-h-0 w-full shrink-0 overflow-hidden";
20574
21671
  const desktopResizeStart = () => {
20575
21672
  setDesktopPanelResizing(true);
20576
21673
  };
20577
21674
  const desktopResizeEnd = () => {
20578
21675
  setDesktopPanelResizing(false);
20579
21676
  };
21677
+ const desktopTerminalResizeStart = () => {
21678
+ setDesktopTerminalResizing(true);
21679
+ };
21680
+ const desktopTerminalResizeEnd = () => {
21681
+ setDesktopTerminalResizing(false);
21682
+ };
20580
21683
  const desktopSideWorkspaceMaxWidth = Math.max(0, desktopAvailableWidth - DESKTOP_MIN_SESSION_WIDTH);
20581
21684
  const setDesktopSideWorkspaceWidth = (nextSize) => {
20582
21685
  if (!desktopFileViewOpen) {
@@ -20585,6 +21688,9 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20585
21688
  }
20586
21689
  setDesktopFileViewWidth(Math.max(DESKTOP_MIN_SIDE_PANEL_WIDTH, nextSize - desktopGitPanelWidth));
20587
21690
  };
21691
+ const setDesktopTerminalHeight = (nextSize) => {
21692
+ setDesktopTerminalPanelHeight(clampDesktopPanelSize(nextSize, Math.min(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopTerminalMaxHeight), desktopTerminalMaxHeight));
21693
+ };
20588
21694
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tabs$1, {
20589
21695
  value: currentTab,
20590
21696
  onValueChange,
@@ -20597,54 +21703,83 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20597
21703
  children: sessionPane
20598
21704
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20599
21705
  ref: desktopLayoutRef,
20600
- className: desktopPanelGroupClassName,
20601
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20602
- "data-desktop-panel": "session",
20603
- className: `h-full min-h-0 min-w-0 shrink-0 overflow-hidden ${desktopTransitionClassName}`,
20604
- style: { width: desktopSideWorkspaceOpen ? `calc(100% - ${desktopSideWorkspaceWidth}px)` : "100%" },
20605
- children: sessionPane
20606
- }), desktopSideWorkspaceRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", {
20607
- "aria-label": "Desktop side workspace",
20608
- "aria-hidden": !desktopSideWorkspaceOpen,
20609
- "data-state": desktopSideWorkspaceOpen ? "open" : "closed",
20610
- "data-desktop-panel": "side-workspace",
20611
- className: `flex h-full min-h-0 min-w-0 shrink-0 overflow-visible bg-background data-[state=closed]:pointer-events-none ${desktopTransitionClassName}`,
20612
- style: { width: `${desktopSideWorkspaceWidth}px` },
20613
- children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20614
- "data-desktop-panel": "git",
20615
- className: `relative h-full min-h-0 min-w-0 shrink-0 overflow-visible ${desktopTransitionClassName}`,
21706
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
21707
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21708
+ className: desktopPanelGroupClassName,
21709
+ style: { height: desktopTerminalOpen ? `calc(100% - ${desktopTerminalVisibleHeight}px)` : "100%" },
21710
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21711
+ "data-desktop-panel": "session",
21712
+ className: `h-full min-h-0 min-w-0 shrink-0 overflow-hidden max-md:w-full! ${desktopTransitionClassName}`,
21713
+ style: { width: desktopSideWorkspaceOpen ? `calc(100% - ${desktopSideWorkspaceWidth}px)` : "100%" },
21714
+ children: sessionPane
21715
+ }), desktopSideWorkspaceRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", {
21716
+ "aria-label": "Desktop side workspace",
21717
+ "aria-hidden": !desktopSideWorkspaceOpen,
21718
+ "data-state": desktopSideWorkspaceOpen ? "open" : "closed",
21719
+ "data-desktop-panel": "side-workspace",
21720
+ className: `hidden h-full min-h-0 min-w-0 shrink-0 overflow-visible bg-background data-[state=closed]:pointer-events-none md:flex ${desktopTransitionClassName}`,
20616
21721
  style: { width: `${desktopSideWorkspaceWidth}px` },
20617
- children: [desktopGitPanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopResizeHandle, {
20618
- label: "Resize right sidebar",
20619
- min: Math.min(DESKTOP_MIN_SIDE_PANEL_WIDTH, desktopSideWorkspaceMaxWidth),
20620
- max: desktopSideWorkspaceMaxWidth,
20621
- size: desktopSideWorkspaceWidth,
20622
- onResize: setDesktopSideWorkspaceWidth,
20623
- onResizeStart: desktopResizeStart,
20624
- onResizeEnd: desktopResizeEnd
20625
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20626
- className: "h-full min-h-0 min-w-0 overflow-hidden",
20627
- children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopGitPanel, {
20628
- viewerContextId,
20629
- sessionStore,
20630
- active: desktopGitPanelOpen,
20631
- composerDiffLineComments,
20632
- rightSidebarStore,
20633
- onAddDiffLineComment,
20634
- onCloseAllFiles: onCloseAllFileViewTabs,
20635
- onCloseFile: onCloseFileViewTab,
20636
- onCloseFilesToRight: onCloseFileViewTabsToRight,
20637
- onCloseOtherFiles: onCloseOtherFileViewTabs,
20638
- onOpenFile: onOpenFileViewTab,
20639
- onReorderFiles: onReorderFileViewTabs
20640
- }) : null
20641
- })]
20642
- }) : null
21722
+ children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21723
+ "data-desktop-panel": "git",
21724
+ className: `relative h-full min-h-0 min-w-0 shrink-0 overflow-visible ${desktopTransitionClassName}`,
21725
+ style: { width: `${desktopSideWorkspaceWidth}px` },
21726
+ children: [desktopGitPanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopResizeHandle, {
21727
+ label: "Resize right sidebar",
21728
+ min: Math.min(DESKTOP_MIN_SIDE_PANEL_WIDTH, desktopSideWorkspaceMaxWidth),
21729
+ max: desktopSideWorkspaceMaxWidth,
21730
+ size: desktopSideWorkspaceWidth,
21731
+ onResize: setDesktopSideWorkspaceWidth,
21732
+ onResizeStart: desktopResizeStart,
21733
+ onResizeEnd: desktopResizeEnd
21734
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21735
+ className: "h-full min-h-0 min-w-0 overflow-hidden",
21736
+ children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopGitPanel, {
21737
+ viewerContextId,
21738
+ sessionStore,
21739
+ active: desktopGitPanelOpen,
21740
+ composerDiffLineComments,
21741
+ rightSidebarStore,
21742
+ onAddDiffLineComment,
21743
+ onCloseAllFiles: onCloseAllFileViewTabs,
21744
+ onCloseFile: onCloseFileViewTab,
21745
+ onCloseFilesToRight: onCloseFileViewTabsToRight,
21746
+ onCloseOtherFiles: onCloseOtherFileViewTabs,
21747
+ onOpenFile: onOpenFileViewTab,
21748
+ onReorderFiles: onReorderFileViewTabs
21749
+ }) : null
21750
+ })]
21751
+ }) : null
21752
+ }) : null]
21753
+ }), desktopTerminalRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
21754
+ "aria-label": "Terminal",
21755
+ "aria-hidden": !desktopTerminalOpen,
21756
+ "data-state": desktopTerminalOpen ? "open" : "closed",
21757
+ "data-desktop-panel": "terminal",
21758
+ className: `relative hidden min-h-0 w-full shrink-0 overflow-visible border-t border-border/70 bg-background data-[state=closed]:pointer-events-none data-[state=closed]:border-transparent md:flex ${desktopTransitionClassName}`,
21759
+ style: { height: `${desktopTerminalVisibleHeight}px` },
21760
+ children: [desktopTerminalOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopVerticalResizeHandle, {
21761
+ label: "Resize terminal",
21762
+ min: Math.min(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopTerminalMaxHeight),
21763
+ max: desktopTerminalMaxHeight,
21764
+ size: desktopTerminalVisibleHeight,
21765
+ onResize: setDesktopTerminalHeight,
21766
+ onResizeStart: desktopTerminalResizeStart,
21767
+ onResizeEnd: desktopTerminalResizeEnd
21768
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21769
+ className: "h-full min-h-0 w-full min-w-0 overflow-hidden",
21770
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalPanel, {
21771
+ active: desktopTerminalOpen,
21772
+ cwd: terminalSession.cwd,
21773
+ onClose: onCloseTerminalPanel,
21774
+ sessionId: terminalSession.sessionId,
21775
+ viewerContextId
21776
+ })
21777
+ })]
20643
21778
  }) : null]
20644
21779
  })]
20645
21780
  });
20646
21781
  }
20647
- var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ actionsRef, defaultNewSessionDirectory, displaySessionCwd, gitPanelOpen, loadingDisplaySessionTitle, currentSessionPinned, displaySettingsStore, isSessionViewLoading, newSessionDirectoryOptions, onToggleGitPanel, sessionStore, viewerContextId }) {
21782
+ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ actionsRef, defaultNewSessionDirectory, displaySessionCwd, gitPanelOpen, terminalPanelOpen, loadingDisplaySessionTitle, currentSessionPinned, displaySettingsStore, isSessionViewLoading, newSessionDirectoryOptions, onToggleGitPanel, onToggleTerminalPanel, sessionStore, viewerContextId }) {
20648
21783
  const sessionHeaderState = useSelector(sessionStore, (sessionState) => ({
20649
21784
  firstMessage: sessionState.firstMessage,
20650
21785
  hideThinkingBlock: sessionState.hideThinkingBlock,
@@ -20653,8 +21788,6 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20653
21788
  sessionStreaming: sessionState.streaming
20654
21789
  }), { compare: shallowRecordEqual });
20655
21790
  const hideToolBlocks = useSelector(displaySettingsStore, (settings) => settings.hideToolBlocks);
20656
- const { isMobile: sidebarIsMobile, openMobile: sidebarOpenMobile, state: sidebarState } = useSidebar();
20657
- const showCollapsedNewSessionButton = !(sidebarIsMobile ? sidebarOpenMobile : sidebarState === "expanded");
20658
21791
  const displaySessionTitle = isSessionViewLoading ? loadingDisplaySessionTitle : getCurrentSessionTitleFromState(sessionHeaderState);
20659
21792
  const moveSessionDirectoryOptions = newSessionDirectoryOptions.filter((option) => option.path !== displaySessionCwd);
20660
21793
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -20663,25 +21796,26 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20663
21796
  className: "relative flex w-full items-center gap-1",
20664
21797
  children: [
20665
21798
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "shrink-0" }),
20666
- showCollapsedNewSessionButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21799
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20667
21800
  title: defaultNewSessionDirectory ? `Create a new session in ${formatDisplayPath$1(defaultNewSessionDirectory)}` : "Create a new session",
20668
21801
  kbd: formatShortcutLabel("Control+N"),
20669
21802
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
20670
21803
  size: "icon",
20671
21804
  variant: "ghost",
20672
- className: "shrink-0",
21805
+ className: "shrink-0 md:group-data-[sidebar-state=expanded]/sidebar-wrapper:hidden",
20673
21806
  "aria-label": "Create a new session",
20674
21807
  onClick: () => {
20675
21808
  actionsRef.current.createSession();
20676
21809
  },
20677
21810
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SquarePen, {})
20678
21811
  })
20679
- }) : null,
21812
+ }),
20680
21813
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20681
21814
  className: "absolute left-1/2 flex w-max max-w-[calc(100%-4rem)] -translate-x-1/2 flex-col items-center justify-center gap-0 text-center",
20682
21815
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20683
21816
  className: "flex max-w-full min-w-0 items-center justify-center gap-1.5",
20684
21817
  children: [!isSessionViewLoading && sessionHeaderState.sessionStreaming ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
21818
+ size: "xs",
20685
21819
  className: "shrink-0",
20686
21820
  "aria-label": "Session streaming"
20687
21821
  }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
@@ -20692,7 +21826,7 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20692
21826
  })
20693
21827
  })]
20694
21828
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20695
- className: "flex max-w-full min-w-0 items-center justify-center gap-x-3",
21829
+ className: "flex max-w-full min-w-0 items-center justify-center gap-x-2",
20696
21830
  children: [displaySessionCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20697
21831
  className: "inline-flex min-w-0 items-center text-xs text-muted-foreground",
20698
21832
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -20830,6 +21964,19 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20830
21964
  })
20831
21965
  ]
20832
21966
  })] }),
21967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21968
+ title: "Toggle terminal",
21969
+ kbd: formatShortcutLabel("Control+`"),
21970
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
21971
+ size: "icon",
21972
+ variant: "ghost",
21973
+ className: "hidden md:inline-flex",
21974
+ "aria-pressed": terminalPanelOpen,
21975
+ "aria-label": "Toggle terminal panel",
21976
+ onClick: onToggleTerminalPanel,
21977
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SquareTerminal, {})
21978
+ })
21979
+ }),
20833
21980
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20834
21981
  title: "Toggle right sidebar",
20835
21982
  kbd: formatShortcutLabel("Control+\\"),
@@ -21175,7 +22322,10 @@ function SidebarSessionItem({ entryKey, directorySessionsStore, activeSessionSto
21175
22322
  },
21176
22323
  children: [entry.streaming ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21177
22324
  className: "absolute top-2.5 left-2 flex size-4 items-center justify-center",
21178
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { "aria-label": "Session streaming" })
22325
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
22326
+ size: "xs",
22327
+ "aria-label": "Session streaming"
22328
+ })
21179
22329
  }) : showUnread ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21180
22330
  className: "absolute top-2.5 left-2 flex size-4 items-center justify-center",
21181
22331
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -21352,7 +22502,10 @@ var DirectorySessionGroup = import_react.memo(function DirectorySessionGroup({ d
21352
22502
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirectoryPathLabel, { path: directory })
21353
22503
  }),
21354
22504
  !searchActive && !overlay ? collapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "size-4 shrink-0 opacity-0 transition-opacity group-hover/directory-label:opacity-100 group-focus-visible/directory-label:opacity-100" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "size-4 shrink-0 opacity-0 transition-opacity group-hover/directory-label:opacity-100 group-focus-visible/directory-label:opacity-100" }) : null,
21355
- showHeaderLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { className: "shrink-0" }) : null
22505
+ showHeaderLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
22506
+ size: "xs",
22507
+ className: "shrink-0"
22508
+ }) : null
21356
22509
  ]
21357
22510
  }),
21358
22511
  onCreateSessionInDirectory && !overlay && !isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
@@ -21422,7 +22575,7 @@ var DirectorySessionGroup = import_react.memo(function DirectorySessionGroup({ d
21422
22575
  ]
21423
22576
  })] }) : directoryHeader, !overlay ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarGroupContent, { children: showLoadingState ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21424
22577
  className: "flex items-center gap-2 p-2 text-sm text-sidebar-foreground/70",
21425
- children: [showLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
22578
+ children: [showLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21426
22579
  className: "size-4",
21427
22580
  "aria-hidden": "true"
21428
22581
  }), "Loading sessions…"]
@@ -22845,11 +23998,21 @@ function usePromptMutationSessionSnapshot(store) {
22845
23998
  function normalizeQueuedStreamingBehavior(streamingBehavior) {
22846
23999
  return streamingBehavior === "followUp" ? "followUp" : "steer";
22847
24000
  }
24001
+ function promptSidebarPreview(message, images) {
24002
+ const text = message.replace(/\s+/g, " ").trim();
24003
+ if (text) return text;
24004
+ if (images.length === 0) return "";
24005
+ return `${images.length.toLocaleString()} image${images.length === 1 ? "" : "s"}`;
24006
+ }
22848
24007
  function createLocalPendingId() {
22849
24008
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `optimistic:${crypto.randomUUID()}`;
22850
24009
  return `optimistic:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
22851
24010
  }
22852
- function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallbackSessionId }) {
24011
+ function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallbackSessionId, targetSessionKey }) {
24012
+ if (targetSessionKey) return buildRequestUrl(path, {
24013
+ contextId,
24014
+ searchParams: { sessionKey: targetSessionKey }
24015
+ });
22853
24016
  const sessionKey = sessionState.sessionKey && !sessionState.sessionKey.startsWith("optimistic:") ? sessionState.sessionKey : void 0;
22854
24017
  const shouldUseSessionKey = Boolean(sessionKey && (sessionState.draft || !sessionState.sessionId));
22855
24018
  return buildRequestUrl(path, {
@@ -22907,16 +24070,17 @@ function movePendingComposerMessage(messages, pendingId, direction) {
22907
24070
  next.splice(targetIndex, 0, movedItem);
22908
24071
  return next;
22909
24072
  }
22910
- function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultNewSessionDirectory, sessionStore, sessionStateRef, draftSessionLoadingOwnerKey, pendingDraftPrompt, pendingDraftFollowUps, awaitingFirstTurn, pendingMessages, composerDiffLineCommentsRef, composerImagesRef, composerTextRef, composerSkillRef, replaceComposerDraft, lastSyncedEditorTextRef, rememberRecentDirectory, prefetchDirectorySessionsIndex, addOptimisticUserMessage, removeOptimisticUserMessage, addOptimisticSidebarSession, removeOptimisticSidebarSession, setSidebarDirectories, setStoredDraftDirectory, setDraftSessionLoadingOwnerKey, setPendingDraftPrompt, setPendingDraftFollowUps, setPendingMessages, setAwaitingFirstTurn, setIsSubmitting, setComposerDiffLineComments, setComposerImages }) {
24073
+ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultNewSessionDirectory, sessionStore, sessionStateRef, draftSessionLoadingOwnerKey, pendingDraftPrompt, pendingDraftFollowUps, awaitingFirstTurn, pendingMessages, composerDiffLineCommentsRef, composerImagesRef, composerTextRef, composerSkillRef, replaceComposerDraft, lastSyncedEditorTextRef, rememberRecentDirectory, prefetchDirectorySessionsIndex, addOptimisticUserMessage, removeOptimisticUserMessage, addOptimisticSidebarSession, removeOptimisticSidebarSession, updateOptimisticSidebarSessionPreview, setSidebarDirectories, setStoredDraftDirectory, setDraftSessionLoadingOwnerKey, setPendingDraftPrompt, setPendingDraftFollowUps, setPendingMessages, setAwaitingFirstTurn, setIsSubmitting, setComposerDiffLineComments, setComposerImages }) {
22911
24074
  const queryClient = useQueryClient();
22912
24075
  const draftSessionLoadingOwnerKeyRef = import_react.useRef(draftSessionLoadingOwnerKey);
24076
+ const pendingNewSessionPromptTargetRef = import_react.useRef(null);
24077
+ const newSessionRequestNonceRef = import_react.useRef(0);
22913
24078
  const pendingDraftPromptRef = import_react.useRef(pendingDraftPrompt);
22914
24079
  const pendingDraftFollowUpsRef = import_react.useRef(pendingDraftFollowUps);
22915
24080
  const pendingMessagesRef = import_react.useRef(pendingMessages);
22916
24081
  const awaitingFirstTurnRef = import_react.useRef(awaitingFirstTurn);
22917
24082
  const awaitingFirstTurnAssistantOutputSignatureRef = import_react.useRef(null);
22918
24083
  const sessionSnapshot = usePromptMutationSessionSnapshot(sessionStore);
22919
- const sessionStreamingRef = import_react.useRef(sessionSnapshot.streaming);
22920
24084
  const flushPendingDraftPromptRef = import_react.useRef(async () => false);
22921
24085
  import_react.useEffect(() => {
22922
24086
  draftSessionLoadingOwnerKeyRef.current = draftSessionLoadingOwnerKey;
@@ -22933,9 +24097,6 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22933
24097
  import_react.useEffect(() => {
22934
24098
  awaitingFirstTurnRef.current = awaitingFirstTurn;
22935
24099
  }, [awaitingFirstTurn]);
22936
- import_react.useEffect(() => {
22937
- sessionStreamingRef.current = sessionSnapshot.streaming;
22938
- }, [sessionSnapshot.streaming]);
22939
24100
  const updatePendingMessages = import_react.useCallback((updater) => {
22940
24101
  setPendingMessages((current) => {
22941
24102
  const next = updater(current);
@@ -22990,6 +24151,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22990
24151
  removeOptimisticUserMessage(nextPrompt.optimisticId);
22991
24152
  removeOptimisticSidebarSession(nextPrompt.optimisticSidebarSessionId);
22992
24153
  for (const followUp of pendingDraftFollowUpsRef.current) removeOptimisticUserMessage(followUp.optimisticId);
24154
+ pendingNewSessionPromptTargetRef.current = null;
22993
24155
  pendingDraftPromptRef.current = null;
22994
24156
  pendingDraftFollowUpsRef.current = [];
22995
24157
  setPendingDraftPrompt(null);
@@ -23062,17 +24224,32 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23062
24224
  const ownerKey = promptDraftKey({ cwd: nextCwd });
23063
24225
  draftSessionLoadingOwnerKeyRef.current = ownerKey;
23064
24226
  setDraftSessionLoadingOwnerKey(ownerKey);
24227
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
24228
+ awaitingFirstTurnRef.current = false;
24229
+ setAwaitingFirstTurn(false);
24230
+ pendingMessagesRef.current = [];
24231
+ setPendingMessages((current) => current.length === 0 ? current : []);
24232
+ const requestNonce = newSessionRequestNonceRef.current + 1;
24233
+ newSessionRequestNonceRef.current = requestNonce;
23065
24234
  try {
23066
- await createSessionRequest({ cwd: nextCwd });
24235
+ const response = await createSessionRequest({ cwd: nextCwd });
24236
+ if (newSessionRequestNonceRef.current !== requestNonce) return true;
24237
+ pendingNewSessionPromptTargetRef.current = {
24238
+ ownerKey,
24239
+ sessionKey: response.sessionKey,
24240
+ cwd: response.cwd
24241
+ };
23067
24242
  if (draftSessionLoadingOwnerKeyRef.current === ownerKey) {
23068
24243
  draftSessionLoadingOwnerKeyRef.current = null;
23069
24244
  setDraftSessionLoadingOwnerKey((current) => current === ownerKey ? null : current);
23070
24245
  }
23071
- if (pendingDraftPromptRef.current?.ownerKey === ownerKey) flushPendingDraftPromptRef.current(ownerKey);
24246
+ if (pendingDraftPromptRef.current?.ownerKey === ownerKey) flushPendingDraftPromptRef.current(ownerKey, response.sessionKey);
23072
24247
  return true;
23073
24248
  } catch (error) {
24249
+ if (newSessionRequestNonceRef.current !== requestNonce) return true;
23074
24250
  if (draftSessionLoadingOwnerKeyRef.current === ownerKey) draftSessionLoadingOwnerKeyRef.current = null;
23075
24251
  setDraftSessionLoadingOwnerKey((current) => current === ownerKey ? null : current);
24252
+ if (pendingNewSessionPromptTargetRef.current?.ownerKey === ownerKey) pendingNewSessionPromptTargetRef.current = null;
23076
24253
  restorePendingDraftPrompt(ownerKey);
23077
24254
  toast.error(error instanceof Error ? error.message : "Failed to create session");
23078
24255
  return false;
@@ -23083,12 +24260,14 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23083
24260
  rememberRecentDirectory,
23084
24261
  restorePendingDraftPrompt,
23085
24262
  sessionStateRef,
24263
+ setAwaitingFirstTurn,
23086
24264
  setDraftSessionLoadingOwnerKey,
24265
+ setPendingMessages,
23087
24266
  setStoredDraftDirectory,
23088
24267
  viewerContextId
23089
24268
  ]);
23090
- const queuePendingDraftPrompt = import_react.useCallback((streamingBehavior) => {
23091
- const ownerKey = draftSessionLoadingOwnerKeyRef.current;
24269
+ const queuePendingDraftPrompt = import_react.useCallback((streamingBehavior, ownerKeyOverride) => {
24270
+ const ownerKey = ownerKeyOverride || draftSessionLoadingOwnerKeyRef.current;
23092
24271
  if (!ownerKey) return false;
23093
24272
  const message = buildComposerPromptMessage({
23094
24273
  text: composerTextRef.current,
@@ -23097,17 +24276,20 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23097
24276
  }).trim();
23098
24277
  const images = composerImagesRef.current.map((image) => ({ ...image }));
23099
24278
  if (!message && images.length === 0) return false;
23100
- if (!(pendingDraftPromptRef.current?.ownerKey === ownerKey ? pendingDraftPromptRef.current : null)) {
24279
+ const currentPendingPrompt = pendingDraftPromptRef.current?.ownerKey === ownerKey ? pendingDraftPromptRef.current : null;
24280
+ const sidebarPreview = promptSidebarPreview(message, images);
24281
+ if (!currentPendingPrompt) {
23101
24282
  const nextPrompt = {
23102
24283
  ownerKey,
23103
24284
  message,
23104
24285
  images,
24286
+ streamingBehavior,
23105
24287
  optimisticId: addOptimisticUserMessage({
23106
24288
  message,
23107
24289
  images,
23108
24290
  queued: false
23109
24291
  }),
23110
- optimisticSidebarSessionId: addOptimisticSidebarSession()
24292
+ optimisticSidebarSessionId: addOptimisticSidebarSession({ lastMessagePreview: sidebarPreview })
23111
24293
  };
23112
24294
  pendingDraftPromptRef.current = nextPrompt;
23113
24295
  setPendingDraftPrompt(nextPrompt);
@@ -23117,6 +24299,10 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23117
24299
  } else {
23118
24300
  const queuedStreamingBehavior = normalizeQueuedStreamingBehavior(streamingBehavior);
23119
24301
  const optimisticId = createLocalPendingId();
24302
+ updateOptimisticSidebarSessionPreview({
24303
+ ...currentPendingPrompt.optimisticSidebarSessionId ? { optimisticSidebarSessionId: currentPendingPrompt.optimisticSidebarSessionId } : {},
24304
+ preview: sidebarPreview
24305
+ });
23120
24306
  const nextFollowUps = [...pendingDraftFollowUpsRef.current, {
23121
24307
  message,
23122
24308
  images,
@@ -23144,15 +24330,17 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23144
24330
  setComposerDiffLineComments,
23145
24331
  setComposerImages,
23146
24332
  setPendingDraftFollowUps,
23147
- setPendingDraftPrompt
24333
+ setPendingDraftPrompt,
24334
+ updateOptimisticSidebarSessionPreview
23148
24335
  ]);
23149
24336
  const promptMutation = useMutation({
23150
- mutationFn: async ({ message, images, streamingBehavior, pendingId, thinkingLevel }) => {
24337
+ mutationFn: async ({ message, images, streamingBehavior, pendingId, thinkingLevel, targetSessionKey, draftTarget }) => {
23151
24338
  if (!viewerContextId) throw new Error("Viewer context unavailable");
23152
24339
  return await fetchJson(buildCurrentSessionRequestUrl("/api/prompt", {
23153
24340
  contextId: viewerContextId,
23154
24341
  sessionState: sessionStateRef.current,
23155
- fallbackSessionId: activeSessionId
24342
+ fallbackSessionId: activeSessionId,
24343
+ targetSessionKey
23156
24344
  }), {
23157
24345
  method: "POST",
23158
24346
  headers: { "content-type": "application/json" },
@@ -23161,7 +24349,9 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23161
24349
  images,
23162
24350
  streamingBehavior,
23163
24351
  pendingId,
23164
- thinkingLevel
24352
+ thinkingLevel,
24353
+ draftOwnerKey: draftTarget?.ownerKey,
24354
+ draftCwd: draftTarget?.cwd
23165
24355
  })
23166
24356
  });
23167
24357
  },
@@ -23182,12 +24372,29 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23182
24372
  diffLineComments: submittedDiffLineComments
23183
24373
  }).trim();
23184
24374
  if (!message && composerImagesRef.current.length === 0) return false;
23185
- const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(sessionStreamingRef.current || awaitingFirstTurnRef.current);
24375
+ const currentSessionState = sessionStateRef.current;
24376
+ const pendingNewSessionPromptTarget = pendingNewSessionPromptTargetRef.current;
24377
+ const currentDraftOwnerKey = currentSessionState.draft ? promptDraftKey(currentSessionState) : "";
24378
+ const implicitTargetSessionKey = !options?.targetSessionKey && currentSessionState.draft && pendingNewSessionPromptTarget && promptDraftKeyMatchesOwner(currentDraftOwnerKey, pendingNewSessionPromptTarget.ownerKey) ? pendingNewSessionPromptTarget.sessionKey : void 0;
24379
+ const targetSessionKey = options?.targetSessionKey || implicitTargetSessionKey;
24380
+ const targetDraft = currentSessionState.draft ? {
24381
+ ownerKey: options?.targetDraftOwnerKey || (targetSessionKey && pendingNewSessionPromptTarget?.sessionKey === targetSessionKey ? pendingNewSessionPromptTarget.ownerKey : currentDraftOwnerKey),
24382
+ cwd: options?.targetDraftCwd || (targetSessionKey && pendingNewSessionPromptTarget?.sessionKey === targetSessionKey ? pendingNewSessionPromptTarget.cwd || currentSessionState.cwd : currentSessionState.cwd)
24383
+ } : void 0;
24384
+ if (currentSessionState.draft && currentSessionState.sessionKey?.startsWith("optimistic:") && !targetSessionKey) {
24385
+ if (!draftSessionLoadingOwnerKeyRef.current && currentDraftOwnerKey) {
24386
+ draftSessionLoadingOwnerKeyRef.current = currentDraftOwnerKey;
24387
+ setDraftSessionLoadingOwnerKey(currentDraftOwnerKey);
24388
+ }
24389
+ return queuePendingDraftPrompt(streamingBehavior, currentDraftOwnerKey);
24390
+ }
24391
+ const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(currentSessionState.streaming || awaitingFirstTurnRef.current);
23186
24392
  const normalizedStreamingBehavior = treatAsQueuedPrompt ? normalizeQueuedStreamingBehavior(streamingBehavior) : void 0;
23187
24393
  const submittedImages = composerImagesRef.current.map((image) => ({ ...image }));
24394
+ const sidebarPreview = promptSidebarPreview(message, submittedImages);
23188
24395
  const queuedPendingId = treatAsQueuedPrompt ? createLocalPendingId() : void 0;
23189
24396
  if (!treatAsQueuedPrompt) {
23190
- awaitingFirstTurnAssistantOutputSignatureRef.current = sessionAssistantOutputSignature(sessionStateRef.current.items);
24397
+ awaitingFirstTurnAssistantOutputSignatureRef.current = sessionAssistantOutputSignature(currentSessionState.items);
23191
24398
  awaitingFirstTurnRef.current = true;
23192
24399
  setAwaitingFirstTurn(true);
23193
24400
  }
@@ -23196,7 +24403,11 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23196
24403
  images: submittedImages,
23197
24404
  queued: false
23198
24405
  }));
23199
- const optimisticSidebarSessionId = options?.optimisticSidebarSessionId ?? (!treatAsQueuedPrompt && sessionStateRef.current.draft ? addOptimisticSidebarSession() : void 0);
24406
+ const optimisticSidebarSessionId = options?.optimisticSidebarSessionId ?? (!treatAsQueuedPrompt && currentSessionState.draft ? addOptimisticSidebarSession({ lastMessagePreview: sidebarPreview }) : void 0);
24407
+ updateOptimisticSidebarSessionPreview({
24408
+ ...optimisticSidebarSessionId ? { optimisticSidebarSessionId } : {},
24409
+ preview: sidebarPreview
24410
+ });
23200
24411
  if (queuedPendingId && normalizedStreamingBehavior) addOptimisticPendingMessage({
23201
24412
  pendingId: queuedPendingId,
23202
24413
  text: message,
@@ -23214,9 +24425,12 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23214
24425
  images: submittedImages,
23215
24426
  streamingBehavior: normalizedStreamingBehavior,
23216
24427
  pendingId: queuedPendingId,
23217
- thinkingLevel: sessionStateRef.current.thinkingLevel
24428
+ thinkingLevel: currentSessionState.thinkingLevel,
24429
+ targetSessionKey,
24430
+ draftTarget: targetDraft
23218
24431
  });
23219
24432
  if (queuedPendingId && "queued" in response && (!response.queued || response.canceled)) removeOptimisticPendingMessage(queuedPendingId);
24433
+ if (targetSessionKey && pendingNewSessionPromptTargetRef.current?.sessionKey === targetSessionKey) pendingNewSessionPromptTargetRef.current = null;
23220
24434
  return true;
23221
24435
  } catch (error) {
23222
24436
  if (queuedPendingId) removeOptimisticPendingMessage(queuedPendingId);
@@ -23259,10 +24473,12 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23259
24473
  setAwaitingFirstTurn,
23260
24474
  setComposerDiffLineComments,
23261
24475
  setComposerImages,
24476
+ setDraftSessionLoadingOwnerKey,
23262
24477
  setIsSubmitting,
24478
+ updateOptimisticSidebarSessionPreview,
23263
24479
  viewerContextId
23264
24480
  ]);
23265
- const flushPendingDraftFollowUps = import_react.useCallback(async () => {
24481
+ const flushPendingDraftFollowUps = import_react.useCallback(async (targetSessionKey, draftTarget) => {
23266
24482
  if (draftSessionLoadingOwnerKeyRef.current) return false;
23267
24483
  const pendingFollowUps = pendingDraftFollowUpsRef.current;
23268
24484
  if (pendingFollowUps.length === 0) return false;
@@ -23283,7 +24499,9 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23283
24499
  images: followUp.images,
23284
24500
  streamingBehavior: followUp.streamingBehavior,
23285
24501
  pendingId: followUp.optimisticId,
23286
- thinkingLevel: sessionStateRef.current.thinkingLevel
24502
+ thinkingLevel: sessionStateRef.current.thinkingLevel,
24503
+ targetSessionKey,
24504
+ draftTarget
23287
24505
  });
23288
24506
  } catch (error) {
23289
24507
  for (const unsentFollowUp of followUps.slice(index)) removeOptimisticUserMessage(unsentFollowUp.optimisticId);
@@ -23306,24 +24524,35 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23306
24524
  setComposerImages,
23307
24525
  setPendingDraftFollowUps
23308
24526
  ]);
23309
- const flushPendingDraftPrompt = import_react.useCallback(async (ownerKey) => {
24527
+ const flushPendingDraftPrompt = import_react.useCallback(async (ownerKey, targetSessionKey) => {
23310
24528
  const nextPrompt = pendingDraftPromptRef.current;
23311
24529
  if (!nextPrompt || nextPrompt.ownerKey !== ownerKey || draftSessionLoadingOwnerKeyRef.current) return false;
24530
+ const pendingTarget = pendingNewSessionPromptTargetRef.current;
24531
+ const resolvedTargetSessionKey = targetSessionKey || (pendingTarget?.ownerKey === ownerKey ? pendingTarget.sessionKey : void 0);
24532
+ const resolvedDraftTarget = {
24533
+ ownerKey,
24534
+ cwd: pendingTarget?.cwd || sessionStateRef.current.cwd
24535
+ };
23312
24536
  pendingDraftPromptRef.current = null;
23313
24537
  setPendingDraftPrompt(null);
23314
24538
  applyPendingDraftPromptToComposer(nextPrompt);
23315
24539
  if (!await submitPrompt(nextPrompt.streamingBehavior, {
23316
24540
  forceFirstPrompt: true,
23317
24541
  optimisticId: nextPrompt.optimisticId,
23318
- optimisticSidebarSessionId: nextPrompt.optimisticSidebarSessionId
24542
+ optimisticSidebarSessionId: nextPrompt.optimisticSidebarSessionId,
24543
+ targetSessionKey: resolvedTargetSessionKey,
24544
+ targetDraftOwnerKey: resolvedDraftTarget.ownerKey,
24545
+ targetDraftCwd: resolvedDraftTarget.cwd
23319
24546
  })) {
23320
24547
  removeOptimisticSidebarSession(nextPrompt.optimisticSidebarSessionId);
23321
24548
  for (const followUp of pendingDraftFollowUpsRef.current) removeOptimisticUserMessage(followUp.optimisticId);
23322
24549
  pendingDraftFollowUpsRef.current = [];
23323
24550
  setPendingDraftFollowUps([]);
24551
+ if (pendingNewSessionPromptTargetRef.current?.ownerKey === ownerKey) pendingNewSessionPromptTargetRef.current = null;
23324
24552
  return false;
23325
24553
  }
23326
- await flushPendingDraftFollowUps();
24554
+ await flushPendingDraftFollowUps(resolvedTargetSessionKey, resolvedDraftTarget);
24555
+ if (pendingNewSessionPromptTargetRef.current?.ownerKey === ownerKey) pendingNewSessionPromptTargetRef.current = null;
23327
24556
  return true;
23328
24557
  }, [
23329
24558
  applyPendingDraftPromptToComposer,
@@ -23341,9 +24570,15 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23341
24570
  if (currentSessionState.sessionKey?.startsWith("optimistic:")) return;
23342
24571
  const currentOwnerKey = promptDraftKey(currentSessionState);
23343
24572
  if (!currentSessionState.draft || !promptDraftKeyMatchesOwner(currentOwnerKey, draftSessionLoadingOwnerKey)) return;
24573
+ const targetSessionKey = currentSessionState.sessionKey?.startsWith("optimistic:") ? void 0 : currentSessionState.sessionKey;
24574
+ if (targetSessionKey) pendingNewSessionPromptTargetRef.current = {
24575
+ ownerKey: draftSessionLoadingOwnerKey,
24576
+ sessionKey: targetSessionKey,
24577
+ cwd: currentSessionState.cwd
24578
+ };
23344
24579
  draftSessionLoadingOwnerKeyRef.current = null;
23345
24580
  setDraftSessionLoadingOwnerKey(null);
23346
- if (pendingDraftPromptRef.current?.ownerKey === draftSessionLoadingOwnerKey) flushPendingDraftPrompt(draftSessionLoadingOwnerKey);
24581
+ if (pendingDraftPromptRef.current?.ownerKey === draftSessionLoadingOwnerKey) flushPendingDraftPrompt(draftSessionLoadingOwnerKey, targetSessionKey);
23347
24582
  }, [
23348
24583
  draftSessionLoadingOwnerKey,
23349
24584
  flushPendingDraftPrompt,
@@ -24750,10 +25985,13 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24750
25985
  ]);
24751
25986
  }
24752
25987
  var DOUBLE_ESCAPE_INTERVAL_MS = 500;
25988
+ function isTerminalTarget(target) {
25989
+ return target instanceof HTMLElement && target.closest("[data-terminal-panel=\"true\"]") !== null;
25990
+ }
24753
25991
  function isEditableTarget(target) {
24754
25992
  if (!(target instanceof HTMLElement)) return false;
24755
25993
  const tagName = target.tagName.toLowerCase();
24756
- return target.isContentEditable || tagName === "input" || tagName === "textarea" || target.closest("[contenteditable=\"true\"]") !== null;
25994
+ return target.isContentEditable || isTerminalTarget(target) || tagName === "input" || tagName === "textarea" || target.closest("[contenteditable=\"true\"]") !== null;
24757
25995
  }
24758
25996
  function isPromptTextareaTarget(target) {
24759
25997
  return target instanceof HTMLTextAreaElement && target.name === "prompt";
@@ -24793,7 +26031,7 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
24793
26031
  };
24794
26032
  const handleEscape = (event) => {
24795
26033
  const context = getShortcutContext(event);
24796
- if (event.repeat || context.modalOpen || event.defaultPrevented) return;
26034
+ if (event.repeat || context.modalOpen || event.defaultPrevented || isTerminalTarget(event.target)) return;
24797
26035
  const now = Date.now();
24798
26036
  if (now - lastEscapeKeyDownAtRef.current <= DOUBLE_ESCAPE_INTERVAL_MS) {
24799
26037
  lastEscapeKeyDownAtRef.current = 0;
@@ -24832,7 +26070,7 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
24832
26070
  };
24833
26071
  const handleFocusPrompt = (event) => {
24834
26072
  const context = getShortcutContext(event);
24835
- if (context.blockingModalOpen || event.defaultPrevented) return;
26073
+ if (context.blockingModalOpen || event.defaultPrevented || isTerminalTarget(event.target)) return;
24836
26074
  event.preventDefault();
24837
26075
  closeCommandPaletteForShortcut(context.commandPaletteOpen);
24838
26076
  shortcutActionsRef.current.focusPrompt();
@@ -24859,12 +26097,19 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
24859
26097
  const key = event.altKey && event.code.startsWith("Key") ? event.code.slice(3).toLowerCase() : event.key.toLowerCase();
24860
26098
  const context = getShortcutContext(event);
24861
26099
  if (context.blockingModalOpen || event.defaultPrevented) return;
26100
+ if (isTerminalTarget(event.target) && !context.commandPaletteOpen && key !== "`") return;
24862
26101
  if (key === "\\") {
24863
26102
  event.preventDefault();
24864
26103
  closeCommandPaletteForShortcut(context.commandPaletteOpen);
24865
26104
  shortcutActionsRef.current.toggleGitPanel();
24866
26105
  return;
24867
26106
  }
26107
+ if (key === "`") {
26108
+ event.preventDefault();
26109
+ closeCommandPaletteForShortcut(context.commandPaletteOpen);
26110
+ shortcutActionsRef.current.toggleTerminalPanel();
26111
+ return;
26112
+ }
24868
26113
  if (key === "k") {
24869
26114
  event.preventDefault();
24870
26115
  shortcutActionsRef.current.openCommandPalette();
@@ -25036,6 +26281,7 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
25036
26281
  })),
25037
26282
  ...[
25038
26283
  { key: "\\" },
26284
+ { key: "`" },
25039
26285
  { key: "K" },
25040
26286
  { key: "P" },
25041
26287
  {
@@ -25104,6 +26350,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25104
26350
  if (!appUiStoreRef.current) appUiStoreRef.current = createPicoStore({
25105
26351
  currentTab: "session",
25106
26352
  gitPanelOpen: false,
26353
+ terminalPanelOpen: false,
25107
26354
  initialLoadingSessionId: sessionId || null,
25108
26355
  loadingSessionId: null
25109
26356
  }, shallowRecordEqual);
@@ -25119,6 +26366,11 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25119
26366
  setStoreField(appUiStore, "gitPanelOpen", nextOpen);
25120
26367
  safeLocalStorageSetItem(RIGHT_SIDEBAR_OPEN_STORAGE_KEY, nextOpen ? "1" : "0");
25121
26368
  }, [appUiStore]);
26369
+ const setTerminalPanelOpen = import_react.useCallback((action) => {
26370
+ const nextOpen = applyStoreAction(appUiStore.state.terminalPanelOpen, action);
26371
+ setStoreField(appUiStore, "terminalPanelOpen", nextOpen);
26372
+ safeLocalStorageSetItem(TERMINAL_PANEL_OPEN_STORAGE_KEY, nextOpen ? "1" : "0");
26373
+ }, [appUiStore]);
25122
26374
  const setLoadingSessionId = import_react.useCallback((action) => {
25123
26375
  setStoreField(appUiStore, "loadingSessionId", action);
25124
26376
  }, [appUiStore]);
@@ -25544,6 +26796,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25544
26796
  sessionKey: currentSessionState.sessionKey
25545
26797
  }), { compare: shallowRecordEqual });
25546
26798
  const gitPanelOpen = useSelector(appUiStore, (state) => state.gitPanelOpen);
26799
+ const terminalPanelOpen = useSelector(appUiStore, (state) => state.terminalPanelOpen);
25547
26800
  const openFileViewTab = (path, options) => {
25548
26801
  if (!path) return;
25549
26802
  batch(() => {
@@ -25640,7 +26893,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25640
26893
  } : currentSession : { draft: true }, sessionId);
25641
26894
  setStoreState(appUiStore, (current) => ({
25642
26895
  ...current,
25643
- currentTab: current.currentTab === "git" ? "session" : current.currentTab,
26896
+ currentTab: current.currentTab === "git" || current.currentTab === "terminal" ? "session" : current.currentTab,
25644
26897
  initialLoadingSessionId: sessionId ? current.initialLoadingSessionId : null,
25645
26898
  loadingSessionId: sessionId && sessionStateRef.current.sessionId !== sessionId ? sessionId : null
25646
26899
  }));
@@ -25652,7 +26905,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25652
26905
  ]);
25653
26906
  import_react.useEffect(() => {
25654
26907
  if (isMobile) return;
25655
- setCurrentTab((tab) => tab === "git" ? "session" : tab);
26908
+ setCurrentTab((tab) => tab === "git" || tab === "terminal" ? "session" : tab);
25656
26909
  }, [isMobile, setCurrentTab]);
25657
26910
  const syncComposerDraft = (value, target = sessionStateRef.current) => {
25658
26911
  const parsed = parseComposerSkillMessage(value);
@@ -25696,6 +26949,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25696
26949
  const applyStoredClientPreferences = () => {
25697
26950
  batch(() => {
25698
26951
  setGitPanelOpen(readStoredRightSidebarOpen());
26952
+ setTerminalPanelOpen(readStoredTerminalPanelOpen());
25699
26953
  setStoredDraftDirectory(readStoredDraftDirectory() || "");
25700
26954
  setSessionDoneSoundEnabled(readStoredSessionDoneSoundEnabled());
25701
26955
  setSessionDoneDesktopNotificationsEnabled(readStoredSessionDoneDesktopNotificationsEnabled());
@@ -25859,6 +27113,20 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25859
27113
  }
25860
27114
  setGitPanelOpen((open) => !open);
25861
27115
  };
27116
+ const toggleTerminalPanel = () => {
27117
+ if (isMobile) {
27118
+ setCurrentTab((tab) => tab === "terminal" ? "session" : "terminal");
27119
+ return;
27120
+ }
27121
+ setTerminalPanelOpen((open) => !open);
27122
+ };
27123
+ const closeTerminalPanel = () => {
27124
+ if (isMobile) {
27125
+ setCurrentTab((tab) => tab === "terminal" ? "session" : tab);
27126
+ return;
27127
+ }
27128
+ setTerminalPanelOpen(false);
27129
+ };
25862
27130
  const openRenameDialog = () => {
25863
27131
  const currentState = sessionStateRef.current;
25864
27132
  if (!currentState.sessionFile) return;
@@ -26177,6 +27445,8 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26177
27445
  title: "New session",
26178
27446
  modified,
26179
27447
  lastUserMessageAt: modified,
27448
+ lastMessageAt: modified,
27449
+ ...options?.lastMessagePreview ? { lastMessagePreview: options.lastMessagePreview } : {},
26180
27450
  streaming: true,
26181
27451
  unread: false,
26182
27452
  optimistic: true
@@ -26223,6 +27493,29 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26223
27493
  sidebarStore.setSidebarSessionSelectionAnchor((current) => current === entryKey ? "" : current);
26224
27494
  }
26225
27495
  }, [sidebarStore]);
27496
+ const updateOptimisticSidebarSessionPreview = import_react.useCallback((options) => {
27497
+ const preview = options.preview.trim();
27498
+ if (!preview) return;
27499
+ const currentState = sessionStateRef.current;
27500
+ const modified = (/* @__PURE__ */ new Date()).toISOString();
27501
+ const sessionId = options.optimisticSidebarSessionId || currentState.sessionId || currentState.sessionKey;
27502
+ const sessionPath = currentState.sessionFile;
27503
+ const previewUpdate = {
27504
+ ...sessionId ? { sessionId } : {},
27505
+ ...sessionPath ? { sessionPath } : {},
27506
+ preview,
27507
+ modified
27508
+ };
27509
+ sidebarStore.setDirectoryIndexDataByPath((current) => updateOptimisticSidebarSessionPreviewEntry(current, previewUpdate));
27510
+ sidebarStore.setSessionsEvent((current) => {
27511
+ if (!current?.directoryIndexes) return current;
27512
+ const directoryIndexes = updateOptimisticSidebarSessionPreviewEntry(current.directoryIndexes, previewUpdate);
27513
+ return directoryIndexes === current.directoryIndexes ? current : {
27514
+ ...current,
27515
+ directoryIndexes
27516
+ };
27517
+ });
27518
+ }, [sessionStateRef, sidebarStore]);
26226
27519
  const clearSelectedSidebarSelection = import_react.useCallback(() => {
26227
27520
  sidebarStore.setSelectedSidebarSessionKeys((current) => current.length === 0 ? current : []);
26228
27521
  sidebarStore.setSidebarSessionSelectionAnchor((current) => current ? "" : current);
@@ -26255,6 +27548,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26255
27548
  removeOptimisticUserMessage,
26256
27549
  addOptimisticSidebarSession,
26257
27550
  removeOptimisticSidebarSession,
27551
+ updateOptimisticSidebarSessionPreview,
26258
27552
  setSidebarDirectories: sidebarStore.setSidebarDirectories,
26259
27553
  setStoredDraftDirectory,
26260
27554
  setDraftSessionLoadingOwnerKey,
@@ -26311,10 +27605,6 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26311
27605
  const createSessionPromise = requestCreateSession(nextCwd);
26312
27606
  handleSelectSession(void 0);
26313
27607
  clearSelectedSidebarSelection();
26314
- if (shouldCloseMobileSidebar) {
26315
- pendingMobileSidebarPromptFocusRef.current = true;
26316
- setOpenMobile(false);
26317
- } else focusPrompt();
26318
27608
  setAwaitingFirstTurn(false);
26319
27609
  setPendingMessages((current) => current.length === 0 ? current : []);
26320
27610
  const nextState = createOptimisticDraftSessionState({
@@ -26325,6 +27615,10 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26325
27615
  sessionStateRef.current = nextState;
26326
27616
  conversationItemsStore.setItems(nextState.items);
26327
27617
  setSessionState(nextState);
27618
+ if (shouldCloseMobileSidebar) {
27619
+ pendingMobileSidebarPromptFocusRef.current = true;
27620
+ setOpenMobile(false);
27621
+ } else focusPrompt();
26328
27622
  if (await createSessionPromise) return;
26329
27623
  if (sessionStateRef.current.sessionKey !== optimisticSessionKey) return;
26330
27624
  sessionStateRef.current = previousState;
@@ -26711,7 +28005,8 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26711
28005
  hasAvailableModels: sessionStateRef.current.availableModels.length > 0,
26712
28006
  isMobile,
26713
28007
  selectedSidebarSessions,
26714
- sessionFile: sessionState.sessionFile
28008
+ sessionFile: sessionState.sessionFile,
28009
+ terminalPanelOpen
26715
28010
  });
26716
28011
  const buildCommandPaletteCommands = () => {
26717
28012
  const commandState = commandPaletteStateRef.current;
@@ -26762,6 +28057,21 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26762
28057
  ],
26763
28058
  onSelect: toggleGitPanel
26764
28059
  },
28060
+ {
28061
+ id: "toggle-terminal",
28062
+ group: "Workspace",
28063
+ title: commandState.isMobile ? "Toggle terminal tab" : commandState.terminalPanelOpen ? "Close terminal" : "Open terminal",
28064
+ description: commandState.isMobile ? "Switch the mobile terminal tab on or off" : "Toggle the bottom terminal panel",
28065
+ shortcut: formatShortcutLabel("Control+`"),
28066
+ keywords: [
28067
+ "terminal",
28068
+ "shell",
28069
+ "console",
28070
+ "bottom",
28071
+ "panel"
28072
+ ],
28073
+ onSelect: toggleTerminalPanel
28074
+ },
26765
28075
  {
26766
28076
  id: "commit-changes",
26767
28077
  group: "Git",
@@ -27126,6 +28436,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27126
28436
  conversationFrameRef.current?.scrollConversationToTop();
27127
28437
  },
27128
28438
  toggleGitPanel,
28439
+ toggleTerminalPanel,
27129
28440
  toggleHideThinking,
27130
28441
  toggleHideToolBlocks,
27131
28442
  cycleThinkingLevel
@@ -27266,12 +28577,14 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27266
28577
  defaultNewSessionDirectory,
27267
28578
  displaySessionCwd,
27268
28579
  gitPanelOpen,
28580
+ terminalPanelOpen,
27269
28581
  loadingDisplaySessionTitle,
27270
28582
  currentSessionPinned,
27271
28583
  displaySettingsStore,
27272
28584
  isSessionViewLoading,
27273
28585
  newSessionDirectoryOptions,
27274
28586
  onToggleGitPanel: toggleGitPanel,
28587
+ onToggleTerminalPanel: toggleTerminalPanel,
27275
28588
  sessionStore,
27276
28589
  viewerContextId
27277
28590
  }),
@@ -27291,6 +28604,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27291
28604
  displaySettingsStore,
27292
28605
  fileInputRef,
27293
28606
  gitPanelOpen,
28607
+ terminalPanelOpen,
27294
28608
  hiddenThinkingPreviewStore,
27295
28609
  isSessionViewLoading,
27296
28610
  isSubmitting,
@@ -27308,6 +28622,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27308
28622
  onAddDiffLineComment: addComposerDiffLineComment,
27309
28623
  onOpenFileViewTab: openFileViewTab,
27310
28624
  onReorderFileViewTabs: reorderFileViewTabs,
28625
+ onCloseTerminalPanel: closeTerminalPanel,
27311
28626
  rightSidebarStore,
27312
28627
  sessionStore,
27313
28628
  store: composerStore,