@alivault/pico 0.12.0 → 0.14.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 (328) 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-DQLwecED.js → index-DBVxXDtw.js} +11 -10
  4. package/.output/public/assets/routes-yUxD2q-l.js +2194 -0
  5. package/.output/public/assets/styles-CUVz7jV9.css +2 -0
  6. package/.output/public/assets/xterm-CCMwI7sy.js +36 -0
  7. package/.output/public/screenshots/pico-ios-dark.png +0 -0
  8. package/.output/public/screenshots/pico-ios-light.png +0 -0
  9. package/.output/public/screenshots/pico-macos-dark.jpg +0 -0
  10. package/.output/public/screenshots/pico-macos-light.jpg +0 -0
  11. package/.output/server/_chunks/ssr-renderer.mjs +3 -3
  12. package/.output/server/_libs/crossws.mjs +2929 -0
  13. package/.output/server/_libs/h3+rou3+srvx.mjs +7 -1
  14. package/.output/server/_libs/lucide-react.mjs +20 -1
  15. package/.output/server/_libs/unctx.mjs +1 -1
  16. package/.output/server/_libs/xterm__addon-fit.mjs +44 -0
  17. package/.output/server/_libs/xterm__xterm.mjs +10234 -0
  18. package/.output/server/_routes/api/terminal/[id]/ws.mjs +8874 -0
  19. package/.output/server/_ssr/{router-D36lUq7O.mjs → router-CN4JMm2A.mjs} +1605 -314
  20. package/.output/server/_ssr/{routes-C8qT6Oju.mjs → routes-BsQ26pbY.mjs} +2377 -730
  21. package/.output/server/_ssr/{routes-9nZumcXQ.mjs → routes-DbjuMLFI.mjs} +176 -26
  22. package/.output/server/_ssr/ssr.mjs +3 -3
  23. package/.output/server/_ssr/start-jIczsXlR.mjs +4 -0
  24. package/.output/server/{_tanstack-start-manifest_v-Cd2ypHmK.mjs → _tanstack-start-manifest_v-Bs4g_j1_.mjs} +8 -4
  25. package/.output/server/index.mjs +594 -501
  26. package/.output/server/node_modules/node-pty/LICENSE +69 -0
  27. package/.output/server/node_modules/node-pty/README.md +165 -0
  28. package/.output/server/node_modules/node-pty/binding.gyp +111 -0
  29. package/.output/server/node_modules/node-pty/build/Makefile +369 -0
  30. package/.output/server/node_modules/node-pty/build/Release/pty.node +0 -0
  31. package/.output/server/node_modules/node-pty/build/binding.Makefile +6 -0
  32. package/.output/server/node_modules/node-pty/build/pty.target.mk +168 -0
  33. package/.output/server/node_modules/node-pty/deps/.editorconfig +2 -0
  34. package/.output/server/node_modules/node-pty/deps/winpty/.drone.yml +17 -0
  35. package/.output/server/node_modules/node-pty/deps/winpty/.gitattributes +19 -0
  36. package/.output/server/node_modules/node-pty/deps/winpty/LICENSE +21 -0
  37. package/.output/server/node_modules/node-pty/deps/winpty/Makefile +166 -0
  38. package/.output/server/node_modules/node-pty/deps/winpty/README.md +151 -0
  39. package/.output/server/node_modules/node-pty/deps/winpty/RELEASES.md +280 -0
  40. package/.output/server/node_modules/node-pty/deps/winpty/VERSION.txt +1 -0
  41. package/.output/server/node_modules/node-pty/deps/winpty/configure +167 -0
  42. package/.output/server/node_modules/node-pty/deps/winpty/misc/BufferResizeTests.cc +90 -0
  43. package/.output/server/node_modules/node-pty/deps/winpty/misc/ChangeScreenBuffer.cc +53 -0
  44. package/.output/server/node_modules/node-pty/deps/winpty/misc/ClearConsole.cc +72 -0
  45. package/.output/server/node_modules/node-pty/deps/winpty/misc/ConinMode.cc +117 -0
  46. package/.output/server/node_modules/node-pty/deps/winpty/misc/ConinMode.ps1 +116 -0
  47. package/.output/server/node_modules/node-pty/deps/winpty/misc/ConoutMode.cc +113 -0
  48. package/.output/server/node_modules/node-pty/deps/winpty/misc/DebugClient.py +42 -0
  49. package/.output/server/node_modules/node-pty/deps/winpty/misc/DebugServer.py +63 -0
  50. package/.output/server/node_modules/node-pty/deps/winpty/misc/DumpLines.py +5 -0
  51. package/.output/server/node_modules/node-pty/deps/winpty/misc/EnableExtendedFlags.txt +46 -0
  52. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP437-Consolas.txt +528 -0
  53. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP437-Lucida.txt +633 -0
  54. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP932.txt +630 -0
  55. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP936.txt +630 -0
  56. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP949.txt +630 -0
  57. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/CP950.txt +630 -0
  58. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt +16 -0
  59. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/Results.txt +4 -0
  60. package/.output/server/node_modules/node-pty/deps/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt +144 -0
  61. package/.output/server/node_modules/node-pty/deps/winpty/misc/FontSurvey.cc +100 -0
  62. package/.output/server/node_modules/node-pty/deps/winpty/misc/FormatChar.h +21 -0
  63. package/.output/server/node_modules/node-pty/deps/winpty/misc/FreezePerfTest.cc +62 -0
  64. package/.output/server/node_modules/node-pty/deps/winpty/misc/GetCh.cc +20 -0
  65. package/.output/server/node_modules/node-pty/deps/winpty/misc/GetConsolePos.cc +41 -0
  66. package/.output/server/node_modules/node-pty/deps/winpty/misc/GetFont.cc +261 -0
  67. package/.output/server/node_modules/node-pty/deps/winpty/misc/IdentifyConsoleWindow.ps1 +51 -0
  68. package/.output/server/node_modules/node-pty/deps/winpty/misc/IsNewConsole.cc +87 -0
  69. package/.output/server/node_modules/node-pty/deps/winpty/misc/MouseInputNotes.txt +90 -0
  70. package/.output/server/node_modules/node-pty/deps/winpty/misc/MoveConsoleWindow.cc +34 -0
  71. package/.output/server/node_modules/node-pty/deps/winpty/misc/Notes.txt +219 -0
  72. package/.output/server/node_modules/node-pty/deps/winpty/misc/OSVersion.cc +27 -0
  73. package/.output/server/node_modules/node-pty/deps/winpty/misc/ScreenBufferFreezeInactive.cc +101 -0
  74. package/.output/server/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest.cc +671 -0
  75. package/.output/server/node_modules/node-pty/deps/winpty/misc/ScreenBufferTest2.cc +151 -0
  76. package/.output/server/node_modules/node-pty/deps/winpty/misc/SelectAllTest.cc +45 -0
  77. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetBufferSize.cc +32 -0
  78. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetCursorPos.cc +10 -0
  79. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetFont.cc +145 -0
  80. package/.output/server/node_modules/node-pty/deps/winpty/misc/SetWindowRect.cc +36 -0
  81. package/.output/server/node_modules/node-pty/deps/winpty/misc/ShowArgv.cc +12 -0
  82. package/.output/server/node_modules/node-pty/deps/winpty/misc/ShowConsoleInput.cc +40 -0
  83. package/.output/server/node_modules/node-pty/deps/winpty/misc/Spew.py +5 -0
  84. package/.output/server/node_modules/node-pty/deps/winpty/misc/TestUtil.cc +172 -0
  85. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnicodeDoubleWidthTest.cc +102 -0
  86. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnicodeWideTest1.cc +246 -0
  87. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnicodeWideTest2.cc +130 -0
  88. package/.output/server/node_modules/node-pty/deps/winpty/misc/UnixEcho.cc +89 -0
  89. package/.output/server/node_modules/node-pty/deps/winpty/misc/Utf16Echo.cc +46 -0
  90. package/.output/server/node_modules/node-pty/deps/winpty/misc/VeryLargeRead.cc +122 -0
  91. package/.output/server/node_modules/node-pty/deps/winpty/misc/VkEscapeTest.cc +56 -0
  92. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win10ResizeWhileFrozen.cc +52 -0
  93. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win10WrapTest1.cc +57 -0
  94. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win10WrapTest2.cc +30 -0
  95. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Echo1.cc +26 -0
  96. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Echo2.cc +19 -0
  97. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Test1.cc +46 -0
  98. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Test2.cc +70 -0
  99. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Test3.cc +78 -0
  100. package/.output/server/node_modules/node-pty/deps/winpty/misc/Win32Write1.cc +44 -0
  101. package/.output/server/node_modules/node-pty/deps/winpty/misc/WindowsBugCrashReader.cc +27 -0
  102. package/.output/server/node_modules/node-pty/deps/winpty/misc/WriteConsole.cc +106 -0
  103. package/.output/server/node_modules/node-pty/deps/winpty/misc/build32.sh +9 -0
  104. package/.output/server/node_modules/node-pty/deps/winpty/misc/build64.sh +9 -0
  105. package/.output/server/node_modules/node-pty/deps/winpty/misc/color-test.sh +212 -0
  106. package/.output/server/node_modules/node-pty/deps/winpty/misc/font-notes.txt +300 -0
  107. package/.output/server/node_modules/node-pty/deps/winpty/misc/winbug-15048.cc +201 -0
  108. package/.output/server/node_modules/node-pty/deps/winpty/ship/build-pty4j-libpty.bat +36 -0
  109. package/.output/server/node_modules/node-pty/deps/winpty/ship/common_ship.py +53 -0
  110. package/.output/server/node_modules/node-pty/deps/winpty/ship/make_msvc_package.py +165 -0
  111. package/.output/server/node_modules/node-pty/deps/winpty/ship/ship.py +108 -0
  112. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Agent.cc +613 -0
  113. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Agent.h +103 -0
  114. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/AgentCreateDesktop.cc +84 -0
  115. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/AgentCreateDesktop.h +28 -0
  116. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.cc +632 -0
  117. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.h +28 -0
  118. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.cc +852 -0
  119. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.h +109 -0
  120. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInputReencoding.cc +121 -0
  121. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleInputReencoding.h +36 -0
  122. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleLine.cc +152 -0
  123. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/ConsoleLine.h +41 -0
  124. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Coord.h +87 -0
  125. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DebugShowInput.cc +239 -0
  126. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DebugShowInput.h +32 -0
  127. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DefaultInputMap.cc +422 -0
  128. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DefaultInputMap.h +28 -0
  129. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/DsrSender.h +30 -0
  130. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/EventLoop.cc +99 -0
  131. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/EventLoop.h +47 -0
  132. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/InputMap.cc +246 -0
  133. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/InputMap.h +114 -0
  134. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/LargeConsoleRead.cc +71 -0
  135. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/LargeConsoleRead.h +68 -0
  136. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/NamedPipe.cc +378 -0
  137. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/NamedPipe.h +125 -0
  138. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Scraper.cc +699 -0
  139. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Scraper.h +103 -0
  140. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/SimplePool.h +75 -0
  141. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/SmallRect.h +143 -0
  142. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Terminal.cc +535 -0
  143. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Terminal.h +69 -0
  144. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/UnicodeEncoding.h +157 -0
  145. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/UnicodeEncodingTest.cc +189 -0
  146. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32Console.cc +107 -0
  147. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32Console.h +67 -0
  148. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32ConsoleBuffer.cc +193 -0
  149. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/Win32ConsoleBuffer.h +99 -0
  150. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/main.cc +114 -0
  151. package/.output/server/node_modules/node-pty/deps/winpty/src/agent/subdir.mk +61 -0
  152. package/.output/server/node_modules/node-pty/deps/winpty/src/configurations.gypi +60 -0
  153. package/.output/server/node_modules/node-pty/deps/winpty/src/debugserver/DebugServer.cc +117 -0
  154. package/.output/server/node_modules/node-pty/deps/winpty/src/debugserver/subdir.mk +41 -0
  155. package/.output/server/node_modules/node-pty/deps/winpty/src/include/winpty.h +242 -0
  156. package/.output/server/node_modules/node-pty/deps/winpty/src/include/winpty_constants.h +131 -0
  157. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/AgentLocation.cc +75 -0
  158. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/AgentLocation.h +28 -0
  159. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/LibWinptyException.h +54 -0
  160. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/WinptyInternal.h +72 -0
  161. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/subdir.mk +46 -0
  162. package/.output/server/node_modules/node-pty/deps/winpty/src/libwinpty/winpty.cc +970 -0
  163. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/AgentMsg.h +38 -0
  164. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/BackgroundDesktop.cc +122 -0
  165. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/BackgroundDesktop.h +73 -0
  166. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/Buffer.cc +103 -0
  167. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/Buffer.h +102 -0
  168. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/DebugClient.cc +187 -0
  169. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/DebugClient.h +38 -0
  170. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/GenRandom.cc +138 -0
  171. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/GenRandom.h +55 -0
  172. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/GetCommitHash.bat +13 -0
  173. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/Mutex.h +54 -0
  174. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/OsModule.h +63 -0
  175. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/OwnedHandle.cc +36 -0
  176. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/OwnedHandle.h +45 -0
  177. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/PrecompiledHeader.h +43 -0
  178. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringBuilder.h +227 -0
  179. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringBuilderTest.cc +114 -0
  180. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringUtil.cc +55 -0
  181. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/StringUtil.h +80 -0
  182. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/TimeMeasurement.h +63 -0
  183. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/UnixCtrlChars.h +45 -0
  184. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/UpdateGenVersion.bat +20 -0
  185. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsSecurity.cc +460 -0
  186. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsSecurity.h +104 -0
  187. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsVersion.cc +252 -0
  188. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WindowsVersion.h +29 -0
  189. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyAssert.cc +55 -0
  190. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyAssert.h +64 -0
  191. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyException.cc +57 -0
  192. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyException.h +43 -0
  193. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyVersion.cc +42 -0
  194. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/WinptyVersion.h +27 -0
  195. package/.output/server/node_modules/node-pty/deps/winpty/src/shared/winpty_snprintf.h +99 -0
  196. package/.output/server/node_modules/node-pty/deps/winpty/src/subdir.mk +5 -0
  197. package/.output/server/node_modules/node-pty/deps/winpty/src/tests/subdir.mk +28 -0
  198. package/.output/server/node_modules/node-pty/deps/winpty/src/tests/trivial_test.cc +158 -0
  199. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/InputHandler.cc +114 -0
  200. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/InputHandler.h +56 -0
  201. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/OutputHandler.cc +80 -0
  202. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/OutputHandler.h +53 -0
  203. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/Util.cc +86 -0
  204. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/Util.h +31 -0
  205. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/WakeupFd.cc +70 -0
  206. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/WakeupFd.h +42 -0
  207. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/main.cc +729 -0
  208. package/.output/server/node_modules/node-pty/deps/winpty/src/unix-adapter/subdir.mk +41 -0
  209. package/.output/server/node_modules/node-pty/deps/winpty/src/winpty.gyp +234 -0
  210. package/.output/server/node_modules/node-pty/deps/winpty/vcbuild.bat +83 -0
  211. package/.output/server/node_modules/node-pty/lib/conpty_console_list_agent.js +16 -0
  212. package/.output/server/node_modules/node-pty/lib/conpty_console_list_agent.js.map +1 -0
  213. package/.output/server/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  214. package/.output/server/node_modules/node-pty/lib/eventEmitter2.js.map +1 -0
  215. package/.output/server/node_modules/node-pty/lib/eventEmitter2.test.js +30 -0
  216. package/.output/server/node_modules/node-pty/lib/eventEmitter2.test.js.map +1 -0
  217. package/.output/server/node_modules/node-pty/lib/index.js +52 -0
  218. package/.output/server/node_modules/node-pty/lib/index.js.map +1 -0
  219. package/.output/server/node_modules/node-pty/lib/interfaces.js +7 -0
  220. package/.output/server/node_modules/node-pty/lib/interfaces.js.map +1 -0
  221. package/.output/server/node_modules/node-pty/lib/shared/conout.js +11 -0
  222. package/.output/server/node_modules/node-pty/lib/shared/conout.js.map +1 -0
  223. package/.output/server/node_modules/node-pty/lib/terminal.js +190 -0
  224. package/.output/server/node_modules/node-pty/lib/terminal.js.map +1 -0
  225. package/.output/server/node_modules/node-pty/lib/terminal.test.js +139 -0
  226. package/.output/server/node_modules/node-pty/lib/terminal.test.js.map +1 -0
  227. package/.output/server/node_modules/node-pty/lib/testUtils.test.js +28 -0
  228. package/.output/server/node_modules/node-pty/lib/testUtils.test.js.map +1 -0
  229. package/.output/server/node_modules/node-pty/lib/types.js +7 -0
  230. package/.output/server/node_modules/node-pty/lib/types.js.map +1 -0
  231. package/.output/server/node_modules/node-pty/lib/unixTerminal.js +346 -0
  232. package/.output/server/node_modules/node-pty/lib/unixTerminal.js.map +1 -0
  233. package/.output/server/node_modules/node-pty/lib/unixTerminal.test.js +351 -0
  234. package/.output/server/node_modules/node-pty/lib/unixTerminal.test.js.map +1 -0
  235. package/.output/server/node_modules/node-pty/lib/utils.js +39 -0
  236. package/.output/server/node_modules/node-pty/lib/utils.js.map +1 -0
  237. package/.output/server/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  238. package/.output/server/node_modules/node-pty/lib/windowsConoutConnection.js.map +1 -0
  239. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.js +320 -0
  240. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.js.map +1 -0
  241. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.test.js +90 -0
  242. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.test.js.map +1 -0
  243. package/.output/server/node_modules/node-pty/lib/windowsTerminal.js +199 -0
  244. package/.output/server/node_modules/node-pty/lib/windowsTerminal.js.map +1 -0
  245. package/.output/server/node_modules/node-pty/lib/windowsTerminal.test.js +219 -0
  246. package/.output/server/node_modules/node-pty/lib/windowsTerminal.test.js.map +1 -0
  247. package/.output/server/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  248. package/.output/server/node_modules/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
  249. package/.output/server/node_modules/node-pty/package.json +64 -0
  250. package/.output/server/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  251. package/.output/server/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  252. package/.output/server/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  253. package/.output/server/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  254. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  255. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  256. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  257. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  258. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  259. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  260. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/pty.node +0 -0
  261. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/pty.pdb +0 -0
  262. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
  263. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty-agent.pdb +0 -0
  264. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
  265. package/.output/server/node_modules/node-pty/prebuilds/win32-arm64/winpty.pdb +0 -0
  266. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  267. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  268. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  269. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  270. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  271. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  272. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/pty.node +0 -0
  273. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/pty.pdb +0 -0
  274. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
  275. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty-agent.pdb +0 -0
  276. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
  277. package/.output/server/node_modules/node-pty/prebuilds/win32-x64/winpty.pdb +0 -0
  278. package/.output/server/node_modules/node-pty/scripts/gen-compile-commands.js +8 -0
  279. package/.output/server/node_modules/node-pty/scripts/increment-version.js +54 -0
  280. package/.output/server/node_modules/node-pty/scripts/post-install.js +80 -0
  281. package/.output/server/node_modules/node-pty/scripts/prebuild.js +34 -0
  282. package/.output/server/node_modules/node-pty/src/conpty_console_list_agent.ts +15 -0
  283. package/.output/server/node_modules/node-pty/src/eventEmitter2.test.ts +30 -0
  284. package/.output/server/node_modules/node-pty/src/eventEmitter2.ts +48 -0
  285. package/.output/server/node_modules/node-pty/src/index.ts +52 -0
  286. package/.output/server/node_modules/node-pty/src/interfaces.ts +130 -0
  287. package/.output/server/node_modules/node-pty/src/native.d.ts +54 -0
  288. package/.output/server/node_modules/node-pty/src/shared/conout.ts +15 -0
  289. package/.output/server/node_modules/node-pty/src/terminal.test.ts +119 -0
  290. package/.output/server/node_modules/node-pty/src/terminal.ts +211 -0
  291. package/.output/server/node_modules/node-pty/src/testUtils.test.ts +23 -0
  292. package/.output/server/node_modules/node-pty/src/tsconfig.json +22 -0
  293. package/.output/server/node_modules/node-pty/src/types.ts +15 -0
  294. package/.output/server/node_modules/node-pty/src/unix/pty.cc +799 -0
  295. package/.output/server/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  296. package/.output/server/node_modules/node-pty/src/unixTerminal.test.ts +367 -0
  297. package/.output/server/node_modules/node-pty/src/unixTerminal.ts +388 -0
  298. package/.output/server/node_modules/node-pty/src/utils.ts +29 -0
  299. package/.output/server/node_modules/node-pty/src/win/conpty.cc +583 -0
  300. package/.output/server/node_modules/node-pty/src/win/conpty.h +41 -0
  301. package/.output/server/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  302. package/.output/server/node_modules/node-pty/src/win/path_util.cc +95 -0
  303. package/.output/server/node_modules/node-pty/src/win/path_util.h +26 -0
  304. package/.output/server/node_modules/node-pty/src/win/winpty.cc +333 -0
  305. package/.output/server/node_modules/node-pty/src/windowsConoutConnection.ts +82 -0
  306. package/.output/server/node_modules/node-pty/src/windowsPtyAgent.test.ts +94 -0
  307. package/.output/server/node_modules/node-pty/src/windowsPtyAgent.ts +321 -0
  308. package/.output/server/node_modules/node-pty/src/windowsTerminal.test.ts +229 -0
  309. package/.output/server/node_modules/node-pty/src/windowsTerminal.ts +203 -0
  310. package/.output/server/node_modules/node-pty/src/worker/conoutSocketWorker.ts +22 -0
  311. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  312. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  313. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  314. package/.output/server/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  315. package/.output/server/node_modules/node-pty/typings/node-pty.d.ts +211 -0
  316. package/.output/server/package.json +1 -0
  317. package/README.md +68 -6
  318. package/bin/pico.mjs +103 -1
  319. package/package.json +14 -7
  320. package/public/screenshots/pico-ios-dark.png +0 -0
  321. package/public/screenshots/pico-ios-light.png +0 -0
  322. package/public/screenshots/pico-macos-dark.jpg +0 -0
  323. package/public/screenshots/pico-macos-light.jpg +0 -0
  324. package/.output/public/assets/routes-DynLhCvj.js +0 -2195
  325. package/.output/public/assets/styles-DJ9ubgcc.css +0 -2
  326. package/.output/server/_ssr/start-Captdpmb.mjs +0 -4
  327. package/scripts/patch-react-jsx-dev-runtime.mjs +0 -93
  328. /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 FolderPlus, B as ChevronsDownUp, C as ListEnd, D as GitBranch, E as GitPullRequestArrow, F as Download, G as ArrowUp, H as ChevronRight, I as Copy, K as ArrowUpToLine, L as Command, M as ExternalLink, N as Ellipsis, O as GitCommitHorizontal, P as EllipsisVertical, S as ListStart, T as ImagePlus, U as ChevronDown, W as Check, X as ArrowDown, Y as ArrowLeft, a as TriangleAlert, b as Minimize2, c as Square, d as Search, f as Plus, g as PanelLeftClose, h as PanelLeftOpen, i as Undo2, j as FileDiff, k as Folder, l as SquarePen, m as PanelLeft, n as WandSparkles, o as Trash2, p as PanelRight, q as ArrowUpRight, r as Upload, s as Tag, t as X, u as Settings2, v as OctagonX, x as Maximize2, y as Minus, z as ChevronsUpDown } 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-9nZumcXQ.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-DbjuMLFI.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-C8qT6Oju.js
25
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-BsQ26pbY.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,
@@ -1096,7 +1098,7 @@ function directoryDialogHasExactMatch(directoryPaths, normalizedQuery) {
1096
1098
  return normalizedPath === normalizedQuery || normalizedDisplayPath === normalizedQuery;
1097
1099
  });
1098
1100
  }
1099
- function splitDisplayPath$1(value) {
1101
+ function splitDisplayPath(value) {
1100
1102
  if (!value) return {
1101
1103
  leading: "",
1102
1104
  trailing: ""
@@ -1119,7 +1121,7 @@ function splitDisplayPath$1(value) {
1119
1121
  }
1120
1122
  function DirectoryPathLabel$1({ path, prefix }) {
1121
1123
  const displayPath = formatDirectoryDisplayPath(path);
1122
- const { leading, trailing } = splitDisplayPath$1(displayPath);
1124
+ const { leading, trailing } = splitDisplayPath(displayPath);
1123
1125
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1124
1126
  className: "flex min-w-0 items-center text-foreground",
1125
1127
  children: [
@@ -2427,7 +2429,7 @@ function formatShortcutLabel(hotkey) {
2427
2429
  function matchesShortcutEvent(event, hotkey) {
2428
2430
  return matchesKeyboardEvent(event, hotkey);
2429
2431
  }
2430
- function tildePath$1(value) {
2432
+ function tildePath(value) {
2431
2433
  return value.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~");
2432
2434
  }
2433
2435
  function normalizeDirectories(directories) {
@@ -2645,6 +2647,8 @@ function AppShellSessionsDialog(props) {
2645
2647
  }
2646
2648
  function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, currentDirectory, knownDirectories, directorySessionsByPath, sessionStatusByKey, activeSessionId, activeSessionPath, onSelectSession, onRenameSession, onDeleteSession, onError }) {
2647
2649
  const isMobile = useIsMobile();
2650
+ const [isGeneratingName, setIsGeneratingName] = import_react.useState(false);
2651
+ const [generatedNamePath, setGeneratedNamePath] = import_react.useState("");
2648
2652
  const [state, dispatch] = import_react.useReducer(sessionsDialogReducer, initialSessionsDialogState);
2649
2653
  const { query, scope, stage, selectedSessionKey, renameValue, localSessionNames, loadedSessionsByPath, loadingByPath } = state;
2650
2654
  const normalizedCurrentDirectory = currentDirectory.trim();
@@ -2673,6 +2677,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2673
2677
  })));
2674
2678
  const sortedSessionItems = flatSessions.toSorted((left, right) => sessionActivityTime(right.entry) - sessionActivityTime(left.entry));
2675
2679
  const selectedSession = selectedSessionKey ? flatSessions.find((session) => session.key === selectedSessionKey)?.entry : void 0;
2680
+ const canGenerateSelectedSessionName = Boolean(selectedSession?.path && !selectedSession.name?.trim() && generatedNamePath !== selectedSession.path);
2676
2681
  const loading = visibleGroups.some((group) => group.loading);
2677
2682
  const browseInputRef = import_react.useRef(null);
2678
2683
  const deletePanelRef = import_react.useRef(null);
@@ -2753,6 +2758,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2753
2758
  };
2754
2759
  const renameSelectedSession = () => {
2755
2760
  if (!selectedSession?.path) return;
2761
+ setGeneratedNamePath("");
2756
2762
  dispatch({
2757
2763
  type: "renameRequested",
2758
2764
  value: selectedSession.title
@@ -2769,6 +2775,28 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2769
2775
  name: nextName
2770
2776
  });
2771
2777
  };
2778
+ const generateSelectedSessionName = async () => {
2779
+ const targetPath = selectedSession?.path;
2780
+ if (!targetPath || isGeneratingName || !canGenerateSelectedSessionName) return;
2781
+ setIsGeneratingName(true);
2782
+ try {
2783
+ const response = await fetchJson(buildRequestUrl("/api/session/name", { contextId: viewerContextId }), {
2784
+ method: "POST",
2785
+ headers: { "content-type": "application/json" },
2786
+ body: JSON.stringify({ path: targetPath })
2787
+ });
2788
+ dispatch({
2789
+ type: "renameValueChanged",
2790
+ value: response.name
2791
+ });
2792
+ setGeneratedNamePath(targetPath);
2793
+ if (response.source === "heuristic" && response.reason) toast.info("Using heuristic session name", { description: response.reason });
2794
+ } catch (error) {
2795
+ onError?.(error);
2796
+ } finally {
2797
+ setIsGeneratingName(false);
2798
+ }
2799
+ };
2772
2800
  const deleteSelectedSession = () => {
2773
2801
  if (!selectedSession?.path) return;
2774
2802
  dispatch({ type: "deleteRequested" });
@@ -2853,7 +2881,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2853
2881
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CommandList, {
2854
2882
  className: "max-h-none min-h-0 flex-1 md:max-h-[min(70vh,32rem)]",
2855
2883
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandEmpty, { children: loading ? "Loading sessions…" : "No sessions found." }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CommandGroup, {
2856
- heading: `${scope === "all" ? "All sessions" : `Current directory: ${tildePath$1(currentDirectories[0] || "")}`}${loading ? " · loading" : ""}`,
2884
+ heading: `${scope === "all" ? "All sessions" : `Current directory: ${tildePath(currentDirectories[0] || "")}`}${loading ? " · loading" : ""}`,
2857
2885
  children: [loading && sortedSessionItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CommandItem, {
2858
2886
  value: "loading:sessions",
2859
2887
  disabled: true,
@@ -2889,7 +2917,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2889
2917
  children: entry.title
2890
2918
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2891
2919
  className: "line-clamp-1 text-xs text-muted-foreground",
2892
- children: tildePath$1(entry.cwd || directory)
2920
+ children: tildePath(entry.cwd || directory)
2893
2921
  })]
2894
2922
  }),
2895
2923
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -2939,25 +2967,37 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2939
2967
  children: [
2940
2968
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2941
2969
  className: "flex items-center gap-2 border-b border-border/70 px-3 py-2",
2942
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
2943
- type: "button",
2944
- variant: "ghost",
2945
- size: "icon-sm",
2946
- onClick: () => {
2947
- dispatch({ type: "browseRequested" });
2948
- },
2949
- "aria-label": "Back to sessions",
2950
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, {})
2951
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2952
- className: "min-w-0 flex-1",
2953
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2954
- className: "truncate text-sm font-medium",
2955
- children: "Rename session"
2956
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2957
- className: "truncate text-xs text-muted-foreground",
2958
- children: selectedSession?.title || "Selected session"
2959
- })]
2960
- })]
2970
+ children: [
2971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
2972
+ type: "button",
2973
+ variant: "ghost",
2974
+ size: "icon-sm",
2975
+ onClick: () => {
2976
+ dispatch({ type: "browseRequested" });
2977
+ },
2978
+ "aria-label": "Back to sessions",
2979
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, {})
2980
+ }),
2981
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2982
+ className: "min-w-0 flex-1",
2983
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2984
+ className: "truncate text-sm font-medium",
2985
+ children: "Rename session"
2986
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2987
+ className: "truncate text-xs text-muted-foreground",
2988
+ children: selectedSession?.title || "Selected session"
2989
+ })]
2990
+ }),
2991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
2992
+ type: "button",
2993
+ variant: "ghost",
2994
+ disabled: isGeneratingName || !canGenerateSelectedSessionName,
2995
+ onClick: () => {
2996
+ generateSelectedSessionName();
2997
+ },
2998
+ children: [isGeneratingName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WandSparkles, {}), "Generate"]
2999
+ })
3000
+ ]
2961
3001
  }),
2962
3002
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2963
3003
  className: "flex min-h-0 flex-1 items-center p-3",
@@ -2976,6 +3016,12 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2976
3016
  dispatch({ type: "browseRequested" });
2977
3017
  return;
2978
3018
  }
3019
+ if (matchesShortcutEvent(event.nativeEvent, "Control+G")) {
3020
+ event.preventDefault();
3021
+ event.stopPropagation();
3022
+ generateSelectedSessionName();
3023
+ return;
3024
+ }
2979
3025
  if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
2980
3026
  event.preventDefault();
2981
3027
  event.stopPropagation();
@@ -2987,13 +3033,20 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2987
3033
  }),
2988
3034
  isMobile ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2989
3035
  className: "hidden flex-wrap items-center gap-x-3 gap-y-1 border-t border-border/70 px-3 py-2 text-xs text-muted-foreground md:flex",
2990
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2991
- className: "inline-flex items-center gap-1",
2992
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Enter" }), " Save"]
2993
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2994
- className: "inline-flex items-center gap-1",
2995
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Esc" }), " Back"]
2996
- })]
3036
+ children: [
3037
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3038
+ className: "inline-flex items-center gap-1",
3039
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: formatShortcutLabel("Control+G") }), " Generate"]
3040
+ }),
3041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3042
+ className: "inline-flex items-center gap-1",
3043
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Enter" }), " Save"]
3044
+ }),
3045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3046
+ className: "inline-flex items-center gap-1",
3047
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Esc" }), " Back"]
3048
+ })
3049
+ ]
2997
3050
  })
2998
3051
  ]
2999
3052
  });
@@ -7576,7 +7629,7 @@ function GitCommitDialogController({ viewerContextId, cwd, openStateRef, ref })
7576
7629
  function FooterKbd({ children }) {
7577
7630
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children });
7578
7631
  }
7579
- function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueChange, onRenameSession }) {
7632
+ function RenameSessionDialog({ open, onOpenChange, renameValue, generatingName, canGenerateName, onRenameValueChange, onGenerateSessionName, onRenameSession }) {
7580
7633
  const isMobile = useIsMobile();
7581
7634
  const renameInputRef = import_react.useRef(null);
7582
7635
  import_react.useEffect(() => {
@@ -7593,13 +7646,22 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7593
7646
  className: "flex min-h-0 flex-1 flex-col",
7594
7647
  children: [
7595
7648
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
7596
- className: "border-b border-border/70 px-3 py-2",
7597
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7598
- className: "truncate text-sm font-medium",
7599
- children: "Rename session"
7600
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7601
- className: "truncate text-xs text-muted-foreground",
7602
- children: "Update the display name shown in the sidebar."
7649
+ className: "flex items-center gap-2 border-b border-border/70 px-3 py-2",
7650
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
7651
+ className: "min-w-0 flex-1",
7652
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7653
+ className: "truncate text-sm font-medium",
7654
+ children: "Rename session"
7655
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7656
+ className: "truncate text-xs text-muted-foreground",
7657
+ children: "Update the display name shown in the sidebar."
7658
+ })]
7659
+ }), isMobile ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
7660
+ type: "button",
7661
+ variant: "ghost",
7662
+ disabled: generatingName || !canGenerateName,
7663
+ onClick: onGenerateSessionName,
7664
+ children: [generatingName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WandSparkles, {}), "Generate"]
7603
7665
  })]
7604
7666
  }),
7605
7667
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -7615,6 +7677,12 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7615
7677
  onOpenChange(false);
7616
7678
  return;
7617
7679
  }
7680
+ if (event.ctrlKey && event.key.toLowerCase() === "g") {
7681
+ event.preventDefault();
7682
+ event.stopPropagation();
7683
+ onGenerateSessionName();
7684
+ return;
7685
+ }
7618
7686
  if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
7619
7687
  event.preventDefault();
7620
7688
  event.stopPropagation();
@@ -7626,13 +7694,24 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7626
7694
  }),
7627
7695
  isMobile ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
7628
7696
  className: "hidden flex-wrap items-center gap-x-3 gap-y-1 border-t border-border/70 px-3 py-2 text-xs text-muted-foreground md:flex",
7629
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7630
- className: "inline-flex items-center gap-1",
7631
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Enter" }), " Save"]
7632
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7633
- className: "inline-flex items-center gap-1",
7634
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Esc" }), " Cancel"]
7635
- })]
7697
+ children: [
7698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7699
+ className: "inline-flex items-center gap-1",
7700
+ children: [
7701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Ctrl" }),
7702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "G" }),
7703
+ " Generate"
7704
+ ]
7705
+ }),
7706
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7707
+ className: "inline-flex items-center gap-1",
7708
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Enter" }), " Save"]
7709
+ }),
7710
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7711
+ className: "inline-flex items-center gap-1",
7712
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Esc" }), " Cancel"]
7713
+ })
7714
+ ]
7636
7715
  })
7637
7716
  ]
7638
7717
  });
@@ -7650,14 +7729,23 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7650
7729
  }),
7651
7730
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerFooter, {
7652
7731
  className: "border-t border-border/70",
7653
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7654
- variant: "outline",
7655
- onClick: () => onOpenChange(false),
7656
- children: "Cancel"
7657
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7658
- onClick: onRenameSession,
7659
- children: "Save"
7660
- })]
7732
+ children: [
7733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
7734
+ variant: "outline",
7735
+ disabled: generatingName || !canGenerateName,
7736
+ onClick: onGenerateSessionName,
7737
+ children: [generatingName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WandSparkles, {}), "Generate"]
7738
+ }),
7739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7740
+ variant: "outline",
7741
+ onClick: () => onOpenChange(false),
7742
+ children: "Cancel"
7743
+ }),
7744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7745
+ onClick: onRenameSession,
7746
+ children: "Save"
7747
+ })
7748
+ ]
7661
7749
  })
7662
7750
  ]
7663
7751
  })
@@ -7672,9 +7760,11 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7672
7760
  children: body
7673
7761
  });
7674
7762
  }
7675
- function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7763
+ function RenameSessionDialogController({ ref, openStateRef, viewerContextId, onRenameSession }) {
7676
7764
  const [open, setOpen] = import_react.useState(false);
7677
7765
  const [renameValue, setRenameValue] = import_react.useState("");
7766
+ const [isGeneratingName, setIsGeneratingName] = import_react.useState(false);
7767
+ const [canGenerateName, setCanGenerateName] = import_react.useState(false);
7678
7768
  const targetPathRef = import_react.useRef("");
7679
7769
  const openRef = import_react.useRef(open);
7680
7770
  const setOpenState = (nextOpen) => {
@@ -7687,8 +7777,27 @@ function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7687
7777
  if (!nextPath) return;
7688
7778
  targetPathRef.current = nextPath;
7689
7779
  setRenameValue(target.title);
7780
+ setCanGenerateName(!target.hasGeneratedName);
7690
7781
  setOpenState(true);
7691
7782
  };
7783
+ const generateSessionName = async () => {
7784
+ if (!viewerContextId || !targetPathRef.current || isGeneratingName || !canGenerateName) return;
7785
+ setIsGeneratingName(true);
7786
+ try {
7787
+ const response = await fetchJson(buildRequestUrl("/api/session/name", { contextId: viewerContextId }), {
7788
+ method: "POST",
7789
+ headers: { "content-type": "application/json" },
7790
+ body: JSON.stringify({ path: targetPathRef.current })
7791
+ });
7792
+ setRenameValue(response.name);
7793
+ setCanGenerateName(false);
7794
+ if (response.source === "heuristic" && response.reason) toast.info("Using heuristic session name", { description: response.reason });
7795
+ } catch (error) {
7796
+ toast.error(error instanceof Error ? error.message : "Failed to generate session name");
7797
+ } finally {
7798
+ setIsGeneratingName(false);
7799
+ }
7800
+ };
7692
7801
  const submitRename = async () => {
7693
7802
  if (await onRenameSession(targetPathRef.current, renameValue) === false) return;
7694
7803
  setOpenState(false);
@@ -7699,7 +7808,8 @@ function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7699
7808
  if (!entry.path) return;
7700
7809
  openTarget({
7701
7810
  path: entry.path,
7702
- title: entry.title || ""
7811
+ title: entry.title || "",
7812
+ hasGeneratedName: Boolean(entry.name?.trim())
7703
7813
  });
7704
7814
  },
7705
7815
  close: () => {
@@ -7713,7 +7823,12 @@ function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7713
7823
  setOpenState(nextOpen);
7714
7824
  },
7715
7825
  renameValue,
7826
+ generatingName: isGeneratingName,
7827
+ canGenerateName: canGenerateName && Boolean(viewerContextId && targetPathRef.current),
7716
7828
  onRenameValueChange: setRenameValue,
7829
+ onGenerateSessionName: () => {
7830
+ generateSessionName();
7831
+ },
7717
7832
  onRenameSession: () => {
7718
7833
  submitRename();
7719
7834
  }
@@ -7817,19 +7932,22 @@ function DeleteSessionsDialog({ open, onOpenChange, title, description, targets,
7817
7932
  })
7818
7933
  ]
7819
7934
  });
7820
- if (isMobile) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Drawer$1, {
7935
+ if (isMobile) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
7821
7936
  open,
7822
7937
  onOpenChange,
7823
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerContent, {
7824
- className: "max-h-[90svh] overflow-hidden",
7938
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
7939
+ className: "max-h-[90svh] overflow-hidden p-0",
7825
7940
  children: [
7826
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerTitle, { children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerDescription, { children: description })] }),
7941
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
7942
+ className: "sr-only",
7943
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: description })]
7944
+ }),
7827
7945
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7828
- className: "flex min-h-0 flex-1 flex-col overflow-hidden px-4 pb-4",
7946
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
7829
7947
  children: body
7830
7948
  }),
7831
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerFooter, {
7832
- className: "border-t border-border/70",
7949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, {
7950
+ className: "flex-col-reverse gap-2 border-t border-border/70 p-3 sm:flex-row",
7833
7951
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7834
7952
  variant: "outline",
7835
7953
  onClick: () => onOpenChange(false),
@@ -8368,10 +8486,11 @@ var AppShellMoveSessionDirectoryDialogHost = import_react.memo(function AppShell
8368
8486
  })
8369
8487
  });
8370
8488
  });
8371
- var AppShellRenameSessionDialogHost = import_react.memo(function AppShellRenameSessionDialogHost({ renameDialogRef, renameOpenRef, renameSessionPath }) {
8489
+ var AppShellRenameSessionDialogHost = import_react.memo(function AppShellRenameSessionDialogHost({ renameDialogRef, renameOpenRef, renameSessionPath, viewerContextId }) {
8372
8490
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RenameSessionDialogController, {
8373
8491
  ref: renameDialogRef,
8374
8492
  openStateRef: renameOpenRef,
8493
+ viewerContextId,
8375
8494
  onRenameSession: renameSessionPath
8376
8495
  });
8377
8496
  });
@@ -8513,7 +8632,8 @@ var AppShellFloatingControllers = import_react.memo(function AppShellFloatingCon
8513
8632
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellRenameSessionDialogHost, {
8514
8633
  renameDialogRef,
8515
8634
  renameOpenRef,
8516
- renameSessionPath
8635
+ renameSessionPath,
8636
+ viewerContextId
8517
8637
  }),
8518
8638
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDeleteSessionsDialogHost, {
8519
8639
  deleteDialogRef,
@@ -8859,7 +8979,7 @@ function pendingMessageEditReducer(state, action) {
8859
8979
  };
8860
8980
  }
8861
8981
  }
8862
- var ComposerPendingMessages = import_react.memo(function ComposerPendingMessages({ currentPendingMessages, onEditPendingMessage, onRemovePendingMessage, onReorderPending }) {
8982
+ var ComposerPendingMessages = import_react.memo(function ComposerPendingMessages({ currentPendingMessages, canStartQueue, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onStartPendingQueue }) {
8863
8983
  const [editState, dispatchEditState] = import_react.useReducer(pendingMessageEditReducer, EMPTY_PENDING_MESSAGE_EDIT_STATE);
8864
8984
  const [openSections, setOpenSections] = import_react.useState([]);
8865
8985
  const { editingPendingId, editText, editError } = editState;
@@ -8898,19 +9018,26 @@ var ComposerPendingMessages = import_react.memo(function ComposerPendingMessages
8898
9018
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionItem, {
8899
9019
  value: "pending-prompts",
8900
9020
  className: "border-0",
8901
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionTrigger, {
8902
- className: "min-h-10 items-center gap-3 px-3 py-2 hover:no-underline",
8903
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
8904
- className: "flex min-w-0 items-center gap-2 text-sm font-medium",
8905
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8906
- className: "truncate",
8907
- children: "Queue"
8908
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
8909
- variant: "outline",
8910
- className: "shrink-0",
8911
- children: currentPendingMessages.length
8912
- })]
8913
- })
9021
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9022
+ className: "flex min-h-10 items-center gap-2 px-3 py-2",
9023
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionTrigger, {
9024
+ className: "min-h-0 flex-1 p-0 hover:no-underline",
9025
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9026
+ className: "flex min-w-0 items-center gap-2 text-sm font-medium",
9027
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9028
+ className: "truncate",
9029
+ children: "Queue"
9030
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
9031
+ variant: "outline",
9032
+ className: "shrink-0",
9033
+ children: currentPendingMessages.length
9034
+ })]
9035
+ })
9036
+ }), canStartQueue ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
9037
+ size: "sm",
9038
+ onClick: onStartPendingQueue,
9039
+ children: "Start"
9040
+ }) : null]
8914
9041
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionContent, {
8915
9042
  className: "p-0",
8916
9043
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -10618,10 +10745,9 @@ var PICO_DIFF_COLOR_CSS = `
10618
10745
  --diffs-bg-deletion-emphasis-override: color-mix(in lab, var(--diffs-bg) 42%, var(--diffs-deletion-base));
10619
10746
  }
10620
10747
 
10621
- @media (max-width: 640px) {
10622
- [data-annotation-content] {
10623
- inline-size: min(100%, calc(100vw - var(--diffs-column-number-width, 0px) - 1rem));
10624
- }
10748
+ [data-overflow="wrap"] [data-annotation-content] {
10749
+ inline-size: var(--diffs-column-content-width, auto);
10750
+ max-inline-size: min(100%, calc(100vw - 1rem));
10625
10751
  }
10626
10752
 
10627
10753
  [data-line-type="change-addition"]:is(
@@ -10712,7 +10838,7 @@ function assistantBlockKey(block) {
10712
10838
  case "text": return `text:${block.text}`;
10713
10839
  case "thinking": return `thinking:${block.text}`;
10714
10840
  case "tool": return `tool:${block.callId || block.name || "tool"}:${toolArgsKey(block.args)}`;
10715
- case "compaction": return `compaction:${block.tokensBefore}:${block.summary}`;
10841
+ case "compaction": return `compaction:${block.tokensBefore}:${block.estimatedTokensAfter ?? ""}:${block.summary}`;
10716
10842
  default: return "block";
10717
10843
  }
10718
10844
  }
@@ -10813,22 +10939,7 @@ function formatToolArgString(value) {
10813
10939
  return trimmed;
10814
10940
  }
10815
10941
  function rawShellCommandText(block) {
10816
- if (block.type !== "tool" || block.name !== "bash") return "";
10817
- if (typeof block.args === "string") {
10818
- const trimmed = block.args.trim();
10819
- if (!trimmed) return "";
10820
- try {
10821
- const parsed = JSON.parse(trimmed);
10822
- if (parsed && typeof parsed === "object" && "command" in parsed) {
10823
- const command = parsed.command;
10824
- return typeof command === "string" ? command.trim() : trimmed;
10825
- }
10826
- } catch {
10827
- return trimmed;
10828
- }
10829
- return trimmed;
10830
- }
10831
- return getToolArgText(normalizeToolArgs(block.args), "command") || toolCommandPreview(block);
10942
+ return block.type === "tool" ? rawShellCommandTextFromTool(block.name, block.args) : "";
10832
10943
  }
10833
10944
  function toolCallText(block) {
10834
10945
  if (block.type !== "tool") return "";
@@ -10862,64 +10973,12 @@ function toolOutputText(block) {
10862
10973
  if (block.isError) return "Tool failed with no output.";
10863
10974
  return "No output available.";
10864
10975
  }
10865
- var EXPLORE_TOOL_NAMES = new Set([
10866
- "read",
10867
- "grep",
10868
- "glob",
10869
- "find",
10870
- "ls",
10871
- "list",
10872
- "rg"
10873
- ]);
10874
- var EXPLORE_SHELL_COMMAND_NAMES = new Set([
10875
- "find",
10876
- "grep",
10877
- "ls",
10878
- "rg"
10879
- ]);
10880
- var SKIPPABLE_SHELL_COMMAND_NAMES = new Set([
10881
- "cd",
10882
- "export",
10883
- "pwd",
10884
- "set"
10885
- ]);
10886
- var SHELL_COMMAND_WRAPPER_NAMES = new Set([
10887
- "command",
10888
- "env",
10889
- "noglob",
10890
- "sudo",
10891
- "time"
10892
- ]);
10893
- function shellCommandNameFromSegment(segment) {
10894
- let text = segment.trim();
10895
- while (text) {
10896
- const assignmentMatch = text.match(/^[A-Za-z_][A-Za-z0-9_]*=\S+\s+/);
10897
- if (assignmentMatch) {
10898
- text = text.slice(assignmentMatch[0].length).trimStart();
10899
- continue;
10900
- }
10901
- const commandMatch = text.match(/^([./A-Za-z0-9_-]+)\b/);
10902
- if (!commandMatch) return "";
10903
- const commandPath = commandMatch[1] || "";
10904
- const commandName = pathBaseName$1(commandPath);
10905
- if (!SHELL_COMMAND_WRAPPER_NAMES.has(commandName)) return commandName;
10906
- text = text.slice(commandPath.length).trimStart();
10907
- }
10908
- return "";
10909
- }
10910
10976
  function exploreShellCommandName(block) {
10911
- if (block.type !== "tool" || block.name !== "bash") return "";
10912
- const segments = rawShellCommandText(block).split(/(?:&&|\|\||;)/);
10913
- for (const segment of segments) {
10914
- const commandName = shellCommandNameFromSegment(segment);
10915
- if (!commandName || SKIPPABLE_SHELL_COMMAND_NAMES.has(commandName)) continue;
10916
- return EXPLORE_SHELL_COMMAND_NAMES.has(commandName) ? commandName : "";
10917
- }
10918
- return "";
10977
+ return block.type === "tool" ? exploreShellCommandNameFromTool(block.name, block.args) : "";
10919
10978
  }
10920
10979
  function matchesExploreToolBlock(block) {
10921
10980
  if (block.type !== "tool") return false;
10922
- return block.category === "explore" || EXPLORE_TOOL_NAMES.has(block.name || "") || Boolean(exploreShellCommandName(block));
10981
+ return block.category === "explore" || toolCategoryFromTool(block.name, block.args) === "explore";
10923
10982
  }
10924
10983
  function isExploreToolBlock(block) {
10925
10984
  return matchesExploreToolBlock(block);
@@ -10927,7 +10986,10 @@ function isExploreToolBlock(block) {
10927
10986
  function isPendingUnclassifiedToolBlock(block) {
10928
10987
  if (block.type !== "tool" || !block.running || matchesExploreToolBlock(block)) return false;
10929
10988
  if (!block.name) return true;
10930
- if (block.name === "bash") return !rawShellCommandText(block);
10989
+ if (block.name === "bash") {
10990
+ const category = toolCategoryFromTool(block.name, block.args);
10991
+ return !rawShellCommandText(block) || category !== "explore" && (toolArgsAreIncompleteJsonObject(block.args) || toolCouldBecomeExploreFromTool(block.name, block.args));
10992
+ }
10931
10993
  return false;
10932
10994
  }
10933
10995
  function getToolArgNumber(args, key) {
@@ -11078,7 +11140,7 @@ function sameAssistantBlock(left, right) {
11078
11140
  return left.callId === right.callId && left.name === right.name && left.category === right.category && left.output === right.output && left.isError === right.isError && left.running === right.running && sameUnknownValue(left.args, right.args) && sameUnknownValue(left.details, right.details);
11079
11141
  case "compaction":
11080
11142
  if (right.type !== "compaction") return false;
11081
- return left.summary === right.summary && left.tokensBefore === right.tokensBefore;
11143
+ return left.summary === right.summary && left.tokensBefore === right.tokensBefore && left.estimatedTokensAfter === right.estimatedTokensAfter;
11082
11144
  default: return false;
11083
11145
  }
11084
11146
  }
@@ -11415,7 +11477,8 @@ function useAssistantToolGroupBodyBlocks(store, keys, enabled) {
11415
11477
  }
11416
11478
  function compactionTriggerText(block) {
11417
11479
  if (block.type !== "compaction") return "Compaction";
11418
- return block.tokensBefore > 0 ? `Compaction: Compacted from ${block.tokensBefore.toLocaleString()} tokens` : "Compaction";
11480
+ if (block.tokensBefore <= 0) return "Compaction";
11481
+ return block.estimatedTokensAfter && block.estimatedTokensAfter > 0 ? `Compaction: ${block.tokensBefore.toLocaleString()} → ~${block.estimatedTokensAfter.toLocaleString()} tokens` : `Compaction: Compacted from ${block.tokensBefore.toLocaleString()} tokens`;
11419
11482
  }
11420
11483
  var ANSI_16_COLORS = [
11421
11484
  "#3f3f46",
@@ -11725,7 +11788,7 @@ function PlainToolOutput({ isError = false, text }) {
11725
11788
  children: text
11726
11789
  });
11727
11790
  }
11728
- function scrollDistanceFromBottom(element) {
11791
+ function scrollDistanceFromBottom$1(element) {
11729
11792
  return element.scrollHeight - element.clientHeight - element.scrollTop;
11730
11793
  }
11731
11794
  function scrollElementToBottom(element) {
@@ -11749,7 +11812,7 @@ function useRunningToolAutoScroll({ contentKey, enabled }) {
11749
11812
  }, [contentKey, enabled]);
11750
11813
  const handleScroll = (event) => {
11751
11814
  if (!enabled) return;
11752
- autoScrollRef.current = scrollDistanceFromBottom(event.currentTarget) < 1;
11815
+ autoScrollRef.current = scrollDistanceFromBottom$1(event.currentTarget) < 1;
11753
11816
  };
11754
11817
  return {
11755
11818
  onScroll: handleScroll,
@@ -12055,53 +12118,67 @@ var UserMessageCard = import_react.memo(function UserMessageCard({ item }) {
12055
12118
  const selectedImage = selectedImageIndex === null ? null : item.images[selectedImageIndex] ?? null;
12056
12119
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12057
12120
  className: "mr-2 ml-auto w-fit max-w-[80%]",
12058
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12059
- 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-['']",
12060
- children: [
12061
- labelText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12062
- className: "mb-2 flex items-center gap-2",
12063
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
12064
- variant: "outline",
12065
- className: "border-primary-foreground/35 bg-primary-foreground/10 text-primary-foreground",
12066
- children: labelText
12067
- })
12068
- }) : null,
12069
- item.text ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12070
- className: "text-sm break-words whitespace-pre-wrap text-primary-foreground",
12071
- children: item.text
12072
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12073
- className: "text-sm text-primary-foreground/80",
12074
- children: "Image prompt"
12075
- }),
12076
- item.images.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12077
- className: "mt-3 flex flex-wrap gap-3",
12078
- children: item.images.map((image, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
12079
- type: "button",
12080
- 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",
12081
- onClick: () => setSelectedImageIndex(index),
12082
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
12083
- src: image.previewUrl,
12084
- alt: "Prompt upload",
12085
- className: "block h-28 max-w-full object-cover"
12086
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12087
- className: "sr-only",
12088
- children: "Open prompt upload"
12089
- })]
12090
- }, `${promptImageKey(image)}:${index}`))
12091
- }) : null
12092
- ]
12093
- }), selectedImage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImageLightbox, {
12094
- open: true,
12095
- onOpenChange: (open) => {
12096
- if (!open) setSelectedImageIndex(null);
12097
- },
12098
- imageSrc: selectedImage.previewUrl,
12099
- imageAlt: "Prompt upload",
12100
- title: "Prompt upload preview",
12101
- description: "Expanded preview of an image attached to a user message."
12102
- }) : null]
12121
+ children: [
12122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12123
+ 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-['']",
12124
+ children: [
12125
+ labelText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12126
+ className: "mb-2 flex items-center gap-2",
12127
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
12128
+ variant: "outline",
12129
+ className: "border-primary-foreground/35 bg-primary-foreground/10 text-primary-foreground",
12130
+ children: labelText
12131
+ })
12132
+ }) : null,
12133
+ item.text ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12134
+ className: "text-sm break-words whitespace-pre-wrap text-primary-foreground",
12135
+ children: item.text
12136
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12137
+ className: "text-sm text-primary-foreground/80",
12138
+ children: "Image prompt"
12139
+ }),
12140
+ item.images.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12141
+ className: "mt-3 flex flex-wrap gap-3",
12142
+ children: item.images.map((image, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
12143
+ type: "button",
12144
+ 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",
12145
+ onClick: () => setSelectedImageIndex(index),
12146
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
12147
+ src: image.previewUrl,
12148
+ alt: "Prompt upload",
12149
+ className: "block h-28 max-w-full object-cover"
12150
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12151
+ className: "sr-only",
12152
+ children: "Open prompt upload"
12153
+ })]
12154
+ }, `${promptImageKey(image)}:${index}`))
12155
+ }) : null
12156
+ ]
12157
+ }),
12158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserMessageFooter, { copyText: item.text }),
12159
+ selectedImage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImageLightbox, {
12160
+ open: true,
12161
+ onOpenChange: (open) => {
12162
+ if (!open) setSelectedImageIndex(null);
12163
+ },
12164
+ imageSrc: selectedImage.previewUrl,
12165
+ imageAlt: "Prompt upload",
12166
+ title: "Prompt upload preview",
12167
+ description: "Expanded preview of an image attached to a user message."
12168
+ }) : null
12169
+ ]
12103
12170
  });
12104
12171
  });
12172
+ function UserMessageFooter({ copyText }) {
12173
+ if (!copyText.trim()) return null;
12174
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12175
+ className: "mt-1 flex justify-end gap-1.5 text-xs font-medium text-muted-foreground",
12176
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageFooterCopyButton, {
12177
+ idleLabel: "Copy user message",
12178
+ text: copyText
12179
+ })
12180
+ });
12181
+ }
12105
12182
  function assistantBlockIsVisible({ block, hideThinking, hideToolBlocks }) {
12106
12183
  switch (block.type) {
12107
12184
  case "text": return Boolean(block.text.trim());
@@ -12200,7 +12277,7 @@ function AssistantBlockGroupsView({ anchorBlockKey, store, streaming }) {
12200
12277
  streaming
12201
12278
  }, descriptor.key)) });
12202
12279
  }
12203
- function AssistantMessageFooterCopyButton({ text }) {
12280
+ function MessageFooterCopyButton({ className, idleLabel, text }) {
12204
12281
  const [copyState, setCopyState] = import_react.useState("idle");
12205
12282
  const canCopy = Boolean(text.trim());
12206
12283
  const copyMessage = async () => {
@@ -12214,13 +12291,13 @@ function AssistantMessageFooterCopyButton({ text }) {
12214
12291
  window.setTimeout(() => setCopyState("idle"), 1400);
12215
12292
  }
12216
12293
  };
12217
- const label = copyState === "copied" ? "Copied" : copyState === "error" ? "Retry copy" : "Copy assistant message";
12294
+ const label = copyState === "copied" ? "Copied" : copyState === "error" ? "Retry copy" : idleLabel;
12218
12295
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
12219
12296
  title: label,
12220
12297
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
12221
12298
  variant: "ghost",
12222
12299
  size: "icon-xs",
12223
- className: "-ml-2 text-muted-foreground",
12300
+ className: cn("text-muted-foreground", className),
12224
12301
  disabled: !canCopy,
12225
12302
  "aria-label": label,
12226
12303
  onClick: copyMessage,
@@ -12234,7 +12311,11 @@ function AssistantMessageFooter({ copyText, modelLabel, streaming }) {
12234
12311
  if (!showCopyButton && !modelLabel) return null;
12235
12312
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12236
12313
  className: "-mt-2 flex items-center gap-1.5 text-xs font-medium text-muted-foreground",
12237
- children: [showCopyButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AssistantMessageFooterCopyButton, { text: copyText }) : null, modelLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12314
+ children: [showCopyButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageFooterCopyButton, {
12315
+ className: "-ml-2",
12316
+ idleLabel: "Copy assistant message",
12317
+ text: copyText
12318
+ }) : null, modelLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12238
12319
  className: "truncate",
12239
12320
  children: modelLabel
12240
12321
  }) : null]
@@ -12264,7 +12345,7 @@ function AssistantMessagesStoreCard({ className, hideFooter = false, store }) {
12264
12345
  store
12265
12346
  ]);
12266
12347
  const shellSnapshot = import_react.useSyncExternalStore(shellStore.subscribe, shellStore.getSnapshot, shellStore.getSnapshot);
12267
- const showFooter = Boolean(!hideFooter && !shellSnapshot.streaming && shellSnapshot.hasFooter);
12348
+ const showFooter = Boolean(!hideFooter && !shellSnapshot.working && shellSnapshot.hasFooter);
12268
12349
  if (!shellSnapshot.hasBlocks && !showFooter) return null;
12269
12350
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12270
12351
  className: cn("flex flex-col gap-4", className),
@@ -12765,7 +12846,7 @@ function getClipboardImageFiles(data) {
12765
12846
  if (itemFiles.length > 0) return itemFiles;
12766
12847
  return Array.from(data.files).filter((file) => file.type.startsWith("image/"));
12767
12848
  }
12768
- function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, centerMessages, contextUsageStore, displaySettingsStore, sessionStore, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, flush = false, topContent, viewerContextId, fileInputRef, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onRunBuiltinSlashCommand, onSelectModel, onSelectThinkingLevel, requestPathCompletions, requestFileCompletions, ref }) {
12849
+ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, canStartPendingQueue, centerMessages, contextUsageStore, displaySettingsStore, sessionStore, isSubmitting, isCompacting, isStreaming, awaitingFirstTurn, disabled = false, flush = false, topContent, viewerContextId, fileInputRef, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onStartPendingQueue, onRunBuiltinSlashCommand, onSelectModel, onSelectThinkingLevel, requestPathCompletions, requestFileCompletions, ref }) {
12769
12850
  const promptRef = import_react.useRef(null);
12770
12851
  const [pickerState, dispatchPickerState] = import_react.useReducer(composerPickerReducer, INITIAL_COMPOSER_PICKER_STATE);
12771
12852
  const { modelPickerOpen, thinkingPickerOpen, modelQuery } = pickerState;
@@ -12792,9 +12873,11 @@ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLi
12792
12873
  topContent,
12793
12874
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComposerPendingMessages, {
12794
12875
  currentPendingMessages,
12876
+ canStartQueue: canStartPendingQueue,
12795
12877
  onEditPendingMessage,
12796
12878
  onRemovePendingMessage,
12797
- onReorderPending
12879
+ onReorderPending,
12880
+ onStartPendingQueue
12798
12881
  }),
12799
12882
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12800
12883
  className: "overflow-visible rounded-[18px] border bg-card",
@@ -12805,6 +12888,7 @@ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLi
12805
12888
  composerSkill,
12806
12889
  composerSyncNonce,
12807
12890
  isSubmitting,
12891
+ isCompacting,
12808
12892
  isStreaming,
12809
12893
  awaitingFirstTurn,
12810
12894
  disabled,
@@ -12888,7 +12972,7 @@ var ComposerAttachments = import_react.memo(function ComposerAttachments({ image
12888
12972
  }, promptImageKey(image)))
12889
12973
  });
12890
12974
  });
12891
- var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, fileInputRef, promptRef, displaySettingsStore, sessionStore, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onRunBuiltinSlashCommand, requestPathCompletions, requestFileCompletions }) {
12975
+ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, isSubmitting, isCompacting, isStreaming, awaitingFirstTurn, disabled = false, fileInputRef, promptRef, displaySettingsStore, sessionStore, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onRunBuiltinSlashCommand, requestPathCompletions, requestFileCompletions }) {
12892
12976
  const slashCommands = useComposerSlashCommands({
12893
12977
  displaySettingsStore,
12894
12978
  sessionStore
@@ -12976,7 +13060,7 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12976
13060
  promptRef
12977
13061
  ]);
12978
13062
  const hasSubmittableContent = hasDraftText || composerDiffLineComments.length > 0 || composerImages.length > 0;
12979
- const responseInFlight = isStreaming || awaitingFirstTurn || isSubmitting;
13063
+ const responseInFlight = isStreaming || isCompacting || awaitingFirstTurn || isSubmitting;
12980
13064
  const acceptFollowUps = responseInFlight;
12981
13065
  const setAssistPointerSelectionSuppressedValue = (next) => {
12982
13066
  assistPointerSelectionSuppressedRef.current = next;
@@ -13609,6 +13693,10 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
13609
13693
  if (actionsRef.current.reorderPendingDraftFollowUp(pendingId, direction)) return;
13610
13694
  actionsRef.current.reorderPending(pendingId, direction);
13611
13695
  });
13696
+ const onStartPendingQueue = useStableEvent(() => {
13697
+ if (snapshotRef.current.disabled) return;
13698
+ actionsRef.current.startPendingQueue();
13699
+ });
13612
13700
  const onRunBuiltinSlashCommand = useStableEvent((name, args) => {
13613
13701
  if (snapshotRef.current.disabled) return;
13614
13702
  actionsRef.current.runBuiltinSlashCommand(name, args);
@@ -13659,11 +13747,13 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
13659
13747
  composerText: snapshot.composerText,
13660
13748
  composerSkill: snapshot.composerSkill,
13661
13749
  composerSyncNonce: snapshot.composerSyncNonce,
13750
+ canStartPendingQueue: snapshot.canStartPendingQueue,
13662
13751
  centerMessages,
13663
13752
  contextUsageStore,
13664
13753
  displaySettingsStore,
13665
13754
  sessionStore,
13666
13755
  isSubmitting: snapshot.isSubmitting,
13756
+ isCompacting: snapshot.isCompacting,
13667
13757
  isStreaming: snapshot.isStreaming,
13668
13758
  awaitingFirstTurn: snapshot.awaitingFirstTurn,
13669
13759
  disabled: snapshot.disabled,
@@ -13680,6 +13770,7 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
13680
13770
  onEditPendingMessage,
13681
13771
  onRemovePendingMessage,
13682
13772
  onReorderPending,
13773
+ onStartPendingQueue,
13683
13774
  onRunBuiltinSlashCommand,
13684
13775
  onSelectModel,
13685
13776
  onSelectThinkingLevel,
@@ -14593,6 +14684,78 @@ function useAppShellMessageScroll({ autoScrollEnabled, isSessionViewLoading, ses
14593
14684
  syncAfterConversationChange
14594
14685
  };
14595
14686
  }
14687
+ var DEFAULT_SCROLL_GRADIENT_MAX_HEIGHT = 4;
14688
+ var EMPTY_SCROLL_GRADIENT_STATE = {
14689
+ bottomHeight: 0,
14690
+ topHeight: 0
14691
+ };
14692
+ function scrollDistanceFromBottom(element) {
14693
+ return Math.max(0, element.scrollHeight - element.scrollTop - element.clientHeight);
14694
+ }
14695
+ function scrollGradientHeight(distanceFromEdge, maxHeight) {
14696
+ return Math.min(maxHeight, Math.max(0, distanceFromEdge));
14697
+ }
14698
+ function scrollGradientState(element, maxHeight) {
14699
+ if (!element) return EMPTY_SCROLL_GRADIENT_STATE;
14700
+ return {
14701
+ bottomHeight: scrollGradientHeight(scrollDistanceFromBottom(element), maxHeight),
14702
+ topHeight: scrollGradientHeight(element.scrollTop, maxHeight)
14703
+ };
14704
+ }
14705
+ function setScrollGradientState(setState, nextState) {
14706
+ setState((currentState) => currentState.bottomHeight === nextState.bottomHeight && currentState.topHeight === nextState.topHeight ? currentState : nextState);
14707
+ }
14708
+ function useScrollGradients({ disabled = false, maxHeight = DEFAULT_SCROLL_GRADIENT_MAX_HEIGHT } = {}) {
14709
+ const elementRef = import_react.useRef(null);
14710
+ const [state, setState] = import_react.useState(EMPTY_SCROLL_GRADIENT_STATE);
14711
+ const syncScrollGradients = import_react.useCallback((element = elementRef.current) => {
14712
+ setScrollGradientState(setState, disabled ? EMPTY_SCROLL_GRADIENT_STATE : scrollGradientState(element, maxHeight));
14713
+ }, [disabled, maxHeight]);
14714
+ const setScrollElement = import_react.useCallback((element) => {
14715
+ elementRef.current = element;
14716
+ syncScrollGradients(element);
14717
+ }, [syncScrollGradients]);
14718
+ const onScroll = import_react.useCallback((event) => {
14719
+ syncScrollGradients(event.currentTarget);
14720
+ }, [syncScrollGradients]);
14721
+ import_react.useLayoutEffect(() => {
14722
+ const element = elementRef.current;
14723
+ syncScrollGradients(element);
14724
+ if (!element || typeof ResizeObserver === "undefined") return;
14725
+ let animationFrame = 0;
14726
+ const scheduleSync = () => {
14727
+ window.cancelAnimationFrame(animationFrame);
14728
+ animationFrame = window.requestAnimationFrame(() => {
14729
+ animationFrame = 0;
14730
+ syncScrollGradients(elementRef.current);
14731
+ });
14732
+ };
14733
+ const resizeObserver = new ResizeObserver(scheduleSync);
14734
+ resizeObserver.observe(element);
14735
+ const content = element.firstElementChild;
14736
+ if (content instanceof HTMLElement) resizeObserver.observe(content);
14737
+ return () => {
14738
+ window.cancelAnimationFrame(animationFrame);
14739
+ resizeObserver.disconnect();
14740
+ };
14741
+ }, [syncScrollGradients]);
14742
+ return {
14743
+ bottomHeight: state.bottomHeight,
14744
+ onScroll,
14745
+ setScrollElement,
14746
+ syncScrollGradients,
14747
+ topHeight: state.topHeight
14748
+ };
14749
+ }
14750
+ function ScrollGradientOverlays({ bottomClassName, bottomHeight, className, topClassName, topHeight }) {
14751
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14752
+ 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),
14753
+ style: { height: topHeight }
14754
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14755
+ 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),
14756
+ style: { height: bottomHeight }
14757
+ })] });
14758
+ }
14596
14759
  function ConversationLatestMessageButton({ conversationItemsStore, draft, onClick, scrollStateStore }) {
14597
14760
  const hasMessages = useConversationHasMessages(conversationItemsStore);
14598
14761
  const isDisabled = useMessageScrollValue(scrollStateStore, (snapshot) => draft || !hasMessages || snapshot.isMessagesNearBottom);
@@ -14659,6 +14822,11 @@ function AppShellConversationFrame({ autoScrollEnabled, children, centerMessages
14659
14822
  isSessionViewLoading,
14660
14823
  sessionState
14661
14824
  });
14825
+ const { bottomHeight: conversationScrollBottomGradientHeight, onScroll: onConversationScrollGradientScroll, setScrollElement: setConversationScrollGradientElement, topHeight: conversationScrollTopGradientHeight } = useScrollGradients({ disabled: isSessionViewLoading });
14826
+ const setMessagesScrollAreaElement = import_react.useCallback((element) => {
14827
+ messagesScrollAreaRef.current = element;
14828
+ setConversationScrollGradientElement(element);
14829
+ }, [messagesScrollAreaRef, setConversationScrollGradientElement]);
14662
14830
  import_react.useImperativeHandle(ref, () => ({
14663
14831
  jumpToNextMessage,
14664
14832
  jumpToPreviousMessage,
@@ -14672,39 +14840,47 @@ function AppShellConversationFrame({ autoScrollEnabled, children, centerMessages
14672
14840
  ]);
14673
14841
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14674
14842
  className: "relative min-h-0 flex-1",
14675
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14676
- ref: messagesScrollAreaRef,
14677
- "data-conversation-viewport": "true",
14678
- tabIndex: 0,
14679
- role: "region",
14680
- "aria-label": "Conversation messages",
14681
- className: "h-full overflow-x-hidden overflow-y-auto overscroll-contain px-4 outline-none [overflow-anchor:none]",
14682
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14683
- ref: messagesContentRef,
14684
- className: "flex min-h-full flex-col",
14685
- children: [
14686
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationScrollRevisionObserver, {
14687
- conversationItemsStore,
14688
- disabled: isSessionViewLoading,
14689
- onRevisionChange: syncAfterConversationChange
14690
- }),
14691
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationContentChangeContext.Provider, {
14692
- value: syncAfterConversationChange,
14693
- children
14694
- }),
14695
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: bottomRef })
14696
- ]
14697
- })
14698
- }), !isSessionViewLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationLatestMessageButton, {
14699
- conversationItemsStore,
14700
- draft: sessionState.draft,
14701
- onClick: scrollConversationToBottom,
14702
- scrollStateStore
14703
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationPreviousMessageButton, {
14704
- centerMessages,
14705
- onClick: jumpToPreviousMessage,
14706
- scrollStateStore
14707
- })] }) : null]
14843
+ children: [
14844
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14845
+ ref: setMessagesScrollAreaElement,
14846
+ "data-conversation-viewport": "true",
14847
+ tabIndex: 0,
14848
+ role: "region",
14849
+ "aria-label": "Conversation messages",
14850
+ className: "h-full overflow-x-hidden overflow-y-auto overscroll-contain px-4 outline-none [overflow-anchor:none]",
14851
+ onScroll: onConversationScrollGradientScroll,
14852
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14853
+ ref: messagesContentRef,
14854
+ className: "flex min-h-full flex-col",
14855
+ children: [
14856
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationScrollRevisionObserver, {
14857
+ conversationItemsStore,
14858
+ disabled: isSessionViewLoading,
14859
+ onRevisionChange: syncAfterConversationChange
14860
+ }),
14861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationContentChangeContext.Provider, {
14862
+ value: syncAfterConversationChange,
14863
+ children
14864
+ }),
14865
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: bottomRef })
14866
+ ]
14867
+ })
14868
+ }),
14869
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
14870
+ bottomHeight: conversationScrollBottomGradientHeight,
14871
+ topHeight: conversationScrollTopGradientHeight
14872
+ }),
14873
+ !isSessionViewLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationLatestMessageButton, {
14874
+ conversationItemsStore,
14875
+ draft: sessionState.draft,
14876
+ onClick: scrollConversationToBottom,
14877
+ scrollStateStore
14878
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationPreviousMessageButton, {
14879
+ centerMessages,
14880
+ onClick: jumpToPreviousMessage,
14881
+ scrollStateStore
14882
+ })] }) : null
14883
+ ]
14708
14884
  });
14709
14885
  }
14710
14886
  function AppShellWorkingIndicatorLabel({ fallbackLabel, hiddenThinkingPreviewStore, useHiddenThinkingPreview }) {
@@ -16762,6 +16938,7 @@ function GitRepositorySummary({ viewerContextId, cwd, active, onBranchClick }) {
16762
16938
  }
16763
16939
  function GitPanelToolbar({ viewerContextId, cwd, active }) {
16764
16940
  const queryClient = useQueryClient();
16941
+ const isMobile = useIsMobile();
16765
16942
  const normalizedCwd = normalizeCwd(cwd);
16766
16943
  const [branchDialogOpen, setBranchDialogOpen] = import_react.useState(false);
16767
16944
  const [commitDialogOpen, setCommitDialogOpen] = import_react.useState(false);
@@ -16875,6 +17052,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16875
17052
  kbd: formatShortcutLabel("Control+C"),
16876
17053
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16877
17054
  variant: "outline",
17055
+ size: isMobile ? "default" : "sm",
16878
17056
  onClick: () => {
16879
17057
  setCommitDialogOpen(true);
16880
17058
  },
@@ -16886,6 +17064,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16886
17064
  kbd: formatShortcutLabel("Control+P"),
16887
17065
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16888
17066
  variant: "outline",
17067
+ size: isMobile ? "default" : "sm",
16889
17068
  disabled: gitActionBusy,
16890
17069
  onClick: () => {
16891
17070
  gitActionMutation.mutate("push");
@@ -16898,6 +17077,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16898
17077
  kbd: formatShortcutLabel("Control+Shift+P"),
16899
17078
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16900
17079
  variant: "outline",
17080
+ size: isMobile ? "default" : "sm",
16901
17081
  disabled: gitActionBusy,
16902
17082
  onClick: () => {
16903
17083
  gitActionMutation.mutate("force-push");
@@ -16910,6 +17090,7 @@ function GitPanelToolbar({ viewerContextId, cwd, active }) {
16910
17090
  kbd: formatShortcutLabel("Alt+P"),
16911
17091
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
16912
17092
  variant: "outline",
17093
+ size: isMobile ? "default" : "sm",
16913
17094
  disabled: gitActionBusy,
16914
17095
  onClick: () => {
16915
17096
  gitActionMutation.mutate("pull");
@@ -17018,26 +17199,6 @@ function AlertDialogCancel({ className, variant = "outline", size = "default", .
17018
17199
  ...props
17019
17200
  });
17020
17201
  }
17021
- function setDerivedScrollState(setState, nextState) {
17022
- setState((current) => Object.is(current, nextState) ? current : nextState);
17023
- }
17024
- function hasScrolledContent(scrollElement) {
17025
- return Boolean(scrollElement && scrollElement.scrollTop > 0);
17026
- }
17027
- function hasRemainingScrollContent(scrollElement) {
17028
- if (!scrollElement) return false;
17029
- return scrollElement.scrollHeight - scrollElement.clientHeight - scrollElement.scrollTop > 1;
17030
- }
17031
- function getStuckScrollTriggerValue({ getValue, scrollElement, selector }) {
17032
- if (!scrollElement || scrollElement.scrollTop <= 0) return "";
17033
- const containerTop = scrollElement.getBoundingClientRect().top;
17034
- const triggers = Array.from(scrollElement.querySelectorAll(selector));
17035
- for (const trigger of triggers) {
17036
- const rect = trigger.getBoundingClientRect();
17037
- if (rect.top <= containerTop + 1 && rect.bottom > containerTop + 1) return getValue(trigger);
17038
- }
17039
- return "";
17040
- }
17041
17202
  function GitSection({ title, meta, controls, className, bodyClassName, children }) {
17042
17203
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
17043
17204
  className: cn("overflow-hidden rounded-xl border border-border/80 bg-card/50", className),
@@ -17081,10 +17242,7 @@ var initialGitCommitRowState = {
17081
17242
  formDialog: null,
17082
17243
  confirmDialog: null,
17083
17244
  contextMenuOpen: false,
17084
- contextMenuKey: 0,
17085
- scrollValue: "",
17086
- commitBodyHeaderShadowed: false,
17087
- commitBodyFooterShadowed: false
17245
+ contextMenuKey: 0
17088
17246
  };
17089
17247
  function gitCommitRowReducer(state, action) {
17090
17248
  switch (action.type) {
@@ -17132,18 +17290,10 @@ function gitCommitRowReducer(state, action) {
17132
17290
  contextMenuOpen: false,
17133
17291
  contextMenuKey: state.contextMenuKey + 1
17134
17292
  };
17135
- case "set-scroll-shadows":
17136
- if (state.scrollValue === action.value && state.commitBodyHeaderShadowed === action.headerShadowed && state.commitBodyFooterShadowed === action.footerShadowed) return state;
17137
- return {
17138
- ...state,
17139
- scrollValue: action.value,
17140
- commitBodyHeaderShadowed: action.headerShadowed,
17141
- commitBodyFooterShadowed: action.footerShadowed
17142
- };
17143
17293
  }
17144
17294
  }
17145
17295
  function gitGraphLaneColor(index, active = false) {
17146
- if (active) return "#f87171";
17296
+ if (active) return "var(--warning)";
17147
17297
  return GIT_GRAPH_LANE_COLORS[index % GIT_GRAPH_LANE_COLORS.length];
17148
17298
  }
17149
17299
  var GitCommitGraphBranch = class {
@@ -17768,8 +17918,8 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17768
17918
  const [rowState, dispatchRow] = import_react.useReducer(gitCommitRowReducer, initialGitCommitRowState);
17769
17919
  const hashCopiedResetRef = import_react.useRef(void 0);
17770
17920
  const messageCopiedResetRef = import_react.useRef(void 0);
17771
- const commitBodyScrollRef = import_react.useRef(null);
17772
17921
  const contextMenuActionsRef = import_react.useRef(null);
17922
+ const { bottomHeight: commitBodyBottomGradientHeight, onScroll: onCommitBodyGradientScroll, setScrollElement: setCommitBodyGradientElement, topHeight: commitBodyTopGradientHeight } = useScrollGradients();
17773
17923
  const title = parsed.subject.trim();
17774
17924
  const time = formatGitCommitDetailTime(parsed.relativeDate);
17775
17925
  const fullTime = formatGitCommitFullDate(parsed.fullDate);
@@ -17778,19 +17928,6 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17778
17928
  const shortHash = parsed.hash || parsed.fullHash.slice(0, 7);
17779
17929
  const canRunCommitAction = Boolean(viewerContextId && cwd && parsed.fullHash);
17780
17930
  const { hashCopied, messageCopied, formDialog, confirmDialog, contextMenuOpen, contextMenuKey } = rowState;
17781
- const commitBodyHeaderShadowed = rowState.scrollValue === value && rowState.commitBodyHeaderShadowed;
17782
- const commitBodyFooterShadowed = rowState.scrollValue === value && rowState.commitBodyFooterShadowed;
17783
- const updateCommitBodyScrollShadows = (scrollElement) => {
17784
- dispatchRow({
17785
- type: "set-scroll-shadows",
17786
- value,
17787
- headerShadowed: hasScrolledContent(scrollElement),
17788
- footerShadowed: hasRemainingScrollContent(scrollElement)
17789
- });
17790
- };
17791
- import_react.useLayoutEffect(() => {
17792
- updateCommitBodyScrollShadows(commitBodyScrollRef.current);
17793
- });
17794
17931
  import_react.useEffect(() => {
17795
17932
  return () => {
17796
17933
  if (typeof hashCopiedResetRef.current === "number") window.clearTimeout(hashCopiedResetRef.current);
@@ -17957,7 +18094,7 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17957
18094
  });
17958
18095
  };
17959
18096
  if (!parsed.hash && !parsed.subject) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
17960
- className: "flex h-8 max-w-full min-w-0 items-center font-mono text-[13px] leading-5",
18097
+ className: "flex h-8 max-w-full min-w-0 items-center text-[13px] leading-5",
17961
18098
  children: parsed.subject ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
17962
18099
  className: "min-w-0 flex-1 truncate text-foreground",
17963
18100
  children: parsed.subject
@@ -17965,7 +18102,7 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17965
18102
  });
17966
18103
  const trigger = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionTrigger, {
17967
18104
  headerClassName: "min-w-0",
17968
- 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"),
18105
+ 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"),
17969
18106
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
17970
18107
  className: "min-w-0 flex-1 truncate pr-8 text-foreground",
17971
18108
  style: { paddingLeft: graphWidth },
@@ -18107,122 +18244,119 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
18107
18244
  }),
18108
18245
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionContent, {
18109
18246
  className: "relative h-[200px] border-b border-border/70 bg-muted/50 p-0",
18110
- children: [
18111
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18112
- ref: commitBodyScrollRef,
18113
- className: "h-full overflow-y-auto pr-2 pb-2",
18114
- onScroll: (event) => {
18115
- updateCommitBodyScrollShadows(event.currentTarget);
18116
- },
18117
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18118
- className: "grid content-start gap-1.5 pt-1 font-mono text-xs leading-4",
18119
- style: { paddingLeft: graphWidth },
18120
- children: [
18121
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18122
- className: "flex min-w-0 flex-wrap items-center gap-1.5 font-sans",
18123
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
18247
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18248
+ ref: setCommitBodyGradientElement,
18249
+ className: "h-full overflow-y-auto pr-2 pb-2",
18250
+ onScroll: onCommitBodyGradientScroll,
18251
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18252
+ className: "grid content-start gap-1.5 pt-1 text-xs leading-4",
18253
+ style: { paddingLeft: graphWidth },
18254
+ children: [
18255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18256
+ className: "flex min-w-0 flex-wrap items-center gap-1.5 font-sans",
18257
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
18258
+ type: "button",
18259
+ size: "xs",
18260
+ variant: "outline",
18261
+ disabled: !canRunCommitAction,
18262
+ onClick: () => openDiff("commit"),
18263
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileDiff, {}), "View diff"]
18264
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitActionsMenu, { ...commitActionProps })]
18265
+ }),
18266
+ parsed.author || time || parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18267
+ className: "flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 text-muted-foreground",
18268
+ children: [
18269
+ parsed.author ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: parsed.author }) : null,
18270
+ parsed.author && time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18271
+ "aria-hidden": true,
18272
+ children: "·"
18273
+ }) : null,
18274
+ time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18275
+ title: fullTime,
18276
+ side: "top",
18277
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: time })
18278
+ }) : null,
18279
+ (parsed.author || time) && parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18280
+ "aria-hidden": true,
18281
+ children: "·"
18282
+ }) : null,
18283
+ parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18284
+ className: "inline-flex min-w-0 items-center gap-1.5",
18285
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18286
+ className: "min-w-0 truncate",
18287
+ children: shortHash
18288
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18289
+ title: hashCopied ? "Copied" : "Copy full commit hash",
18290
+ side: "top",
18291
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18292
+ type: "button",
18293
+ "aria-label": "Copy full commit hash",
18294
+ className: "inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
18295
+ onClick: (event) => {
18296
+ event.preventDefault();
18297
+ event.stopPropagation();
18298
+ copyFullHashInline();
18299
+ },
18300
+ 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" })
18301
+ })
18302
+ })]
18303
+ }) : null
18304
+ ]
18305
+ }) : null,
18306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18307
+ className: "max-w-full break-words whitespace-normal text-foreground",
18308
+ children: [title || "No commit message", title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18309
+ title: messageCopied ? "Copied" : "Copy commit message",
18310
+ side: "top",
18311
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18124
18312
  type: "button",
18125
- size: "xs",
18126
- variant: "outline",
18127
- disabled: !canRunCommitAction,
18128
- onClick: () => openDiff("commit"),
18129
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileDiff, {}), "View diff"]
18130
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitActionsMenu, { ...commitActionProps })]
18131
- }),
18132
- parsed.author || time || parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18133
- className: "flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 text-muted-foreground",
18313
+ "aria-label": "Copy commit message",
18314
+ 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",
18315
+ onClick: (event) => {
18316
+ event.preventDefault();
18317
+ event.stopPropagation();
18318
+ copyCommitMessageInline();
18319
+ },
18320
+ 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" })
18321
+ })
18322
+ }) : null]
18323
+ }),
18324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18325
+ className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1",
18326
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18327
+ className: "text-emerald-500",
18134
18328
  children: [
18135
- parsed.author ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: parsed.author }) : null,
18136
- parsed.author && time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18137
- "aria-hidden": true,
18138
- children: "·"
18139
- }) : null,
18140
- time ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18141
- title: fullTime,
18142
- side: "top",
18143
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: time })
18144
- }) : null,
18145
- (parsed.author || time) && parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18146
- "aria-hidden": true,
18147
- children: "·"
18148
- }) : null,
18149
- parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18150
- className: "inline-flex min-w-0 items-center gap-1.5",
18151
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18152
- className: "min-w-0 truncate",
18153
- children: shortHash
18154
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18155
- title: hashCopied ? "Copied" : "Copy full commit hash",
18156
- side: "top",
18157
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18158
- type: "button",
18159
- "aria-label": "Copy full commit hash",
18160
- className: "inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
18161
- onClick: (event) => {
18162
- event.preventDefault();
18163
- event.stopPropagation();
18164
- copyFullHashInline();
18165
- },
18166
- 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" })
18167
- })
18168
- })]
18169
- }) : null
18329
+ "+",
18330
+ insertions,
18331
+ " addition",
18332
+ insertions === 1 ? "" : "s"
18170
18333
  ]
18171
- }) : null,
18172
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18173
- className: "max-w-full break-words whitespace-normal text-foreground",
18174
- children: [title || "No commit message", title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
18175
- title: messageCopied ? "Copied" : "Copy commit message",
18176
- side: "top",
18177
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
18178
- type: "button",
18179
- "aria-label": "Copy commit message",
18180
- 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",
18181
- onClick: (event) => {
18182
- event.preventDefault();
18183
- event.stopPropagation();
18184
- copyCommitMessageInline();
18185
- },
18186
- 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" })
18187
- })
18188
- }) : null]
18189
- }),
18190
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18191
- className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1",
18192
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18193
- className: "text-emerald-500",
18194
- children: [
18195
- "+",
18196
- insertions,
18197
- " addition",
18198
- insertions === 1 ? "" : "s"
18199
- ]
18200
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18201
- className: "text-red-400",
18202
- children: [
18203
- "-",
18204
- deletions,
18205
- " deletion",
18206
- deletions === 1 ? "" : "s"
18207
- ]
18208
- })]
18209
- }),
18210
- parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitFilesList, {
18211
- viewerContextId,
18212
- cwd,
18213
- commit: parsed.fullHash,
18214
- shortHash,
18215
- previousShortHash: parsed.parents[0]?.slice(0, 7) || "",
18216
- subject: title,
18217
- disabled: !canRunCommitAction,
18218
- onOpenCommitDiff
18219
- }) : null
18220
- ]
18221
- })
18222
- }),
18223
- /* @__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") }),
18224
- /* @__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") })
18225
- ]
18334
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18335
+ className: "text-red-400",
18336
+ children: [
18337
+ "-",
18338
+ deletions,
18339
+ " deletion",
18340
+ deletions === 1 ? "" : "s"
18341
+ ]
18342
+ })]
18343
+ }),
18344
+ parsed.fullHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitFilesList, {
18345
+ viewerContextId,
18346
+ cwd,
18347
+ commit: parsed.fullHash,
18348
+ shortHash,
18349
+ previousShortHash: parsed.parents[0]?.slice(0, 7) || "",
18350
+ subject: title,
18351
+ disabled: !canRunCommitAction,
18352
+ onOpenCommitDiff
18353
+ }) : null
18354
+ ]
18355
+ })
18356
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
18357
+ bottomHeight: commitBodyBottomGradientHeight,
18358
+ topHeight: commitBodyTopGradientHeight
18359
+ })]
18226
18360
  }),
18227
18361
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
18228
18362
  open: formDialog !== null,
@@ -18473,6 +18607,22 @@ function GitCommitsSection({ viewerContextId, cwd, active, onOpenCommitDiff, onR
18473
18607
  children: content
18474
18608
  });
18475
18609
  }
18610
+ function setDerivedScrollState(setState, nextState) {
18611
+ setState((current) => Object.is(current, nextState) ? current : nextState);
18612
+ }
18613
+ function hasScrolledContent(scrollElement) {
18614
+ return Boolean(scrollElement && scrollElement.scrollTop > 0);
18615
+ }
18616
+ function getStuckScrollTriggerValue({ getValue, scrollElement, selector }) {
18617
+ if (!scrollElement || scrollElement.scrollTop <= 0) return "";
18618
+ const containerTop = scrollElement.getBoundingClientRect().top;
18619
+ const triggers = Array.from(scrollElement.querySelectorAll(selector));
18620
+ for (const trigger of triggers) {
18621
+ const rect = trigger.getBoundingClientRect();
18622
+ if (rect.top <= containerTop + 1 && rect.bottom > containerTop + 1) return getValue(trigger);
18623
+ }
18624
+ return "";
18625
+ }
18476
18626
  function GitFileStatus({ status }) {
18477
18627
  const [indexCharacter, worktreeCharacter] = gitFileStatusCharacters(status);
18478
18628
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -18576,7 +18726,7 @@ function ReviewDiffPendingLineComment({ comment }) {
18576
18726
  comment.onSubmit(normalizedText);
18577
18727
  };
18578
18728
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
18579
- 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",
18729
+ className: "m-2 w-[calc(100%_-_1rem)] max-w-[38rem] rounded-lg border border-border/70 bg-background p-2 shadow-sm",
18580
18730
  onPointerDown: (event) => event.stopPropagation(),
18581
18731
  onSubmit: (event) => {
18582
18732
  event.preventDefault();
@@ -18776,6 +18926,11 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18776
18926
  });
18777
18927
  };
18778
18928
  const changesScrollRef = import_react.useRef(null);
18929
+ const { bottomHeight: changesScrollBottomGradientHeight, onScroll: onChangesScrollGradientScroll, setScrollElement: setChangesScrollGradientElement, topHeight: changesScrollTopGradientHeight } = useScrollGradients();
18930
+ const setChangesScrollElement = import_react.useCallback((element) => {
18931
+ changesScrollRef.current = element;
18932
+ setChangesScrollGradientElement(element);
18933
+ }, [setChangesScrollGradientElement]);
18779
18934
  const reviewContentRef = import_react.useRef(null);
18780
18935
  const historyPanelRef = import_react.useRef(null);
18781
18936
  const historyScrollRef = import_react.useRef(null);
@@ -18974,7 +19129,7 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
18974
19129
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuTrigger, {
18975
19130
  render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
18976
19131
  variant: "outline",
18977
- size: "icon",
19132
+ size: isMobile ? "icon" : "icon-sm",
18978
19133
  "aria-label": "Diff actions",
18979
19134
  disabled: bulkActionDisabled
18980
19135
  }),
@@ -19012,7 +19167,7 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
19012
19167
  title: hasOpenFile ? "Collapse all" : "Expand all",
19013
19168
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
19014
19169
  variant: "outline",
19015
- size: "icon",
19170
+ size: isMobile ? "icon" : "icon-sm",
19016
19171
  "aria-label": hasOpenFile ? "Collapse all" : "Expand all",
19017
19172
  disabled: changedFiles.length === 0,
19018
19173
  onClick: toggleAll,
@@ -19021,6 +19176,7 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
19021
19176
  }),
19022
19177
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleGroup$1, {
19023
19178
  variant: "outline",
19179
+ size: isMobile ? "default" : "sm",
19024
19180
  value: [diffStyle],
19025
19181
  onValueChange: (values) => {
19026
19182
  const value = values[0];
@@ -19037,54 +19193,61 @@ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineCommen
19037
19193
  ]
19038
19194
  })]
19039
19195
  }),
19040
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19041
- ref: changesScrollRef,
19042
- className: "min-h-0 flex-1 overflow-auto",
19043
- onScroll: (event) => {
19044
- updateStickyReviewFileHeader(event.currentTarget);
19045
- },
19046
- children: !normalizedCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19047
- className: "px-3 py-2.5",
19048
- children: "No directory selected."
19049
- }) : !viewerContextId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19050
- className: "px-3 py-2.5",
19051
- children: "Waiting for viewer context…"
19052
- }) : filesQuery.isPending && typeof files === "undefined" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, {
19053
- className: "px-3 py-2.5",
19054
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading changes…"]
19055
- }) : filesQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19056
- tone: "destructive",
19057
- className: "px-3 py-2.5",
19058
- children: getErrorMessage(filesQuery.error, "Failed to load changes")
19059
- }) : files === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19060
- className: "px-3 py-2.5",
19061
- children: "No git repository detected."
19062
- }) : changedFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Accordion$1, {
19063
- multiple: true,
19064
- value: openFiles,
19065
- onValueChange: (nextOpenFiles) => {
19066
- dispatch({
19067
- type: "openFilesChanged",
19068
- openFiles: nextOpenFiles
19069
- });
19196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19197
+ className: "relative min-h-0 flex-1",
19198
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19199
+ ref: setChangesScrollElement,
19200
+ className: "h-full overflow-auto",
19201
+ onScroll: (event) => {
19202
+ updateStickyReviewFileHeader(event.currentTarget);
19203
+ onChangesScrollGradientScroll(event);
19070
19204
  },
19071
- className: "border-b border-border/80",
19072
- children: changedFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewFileAccordionItem, {
19073
- viewerContextId,
19074
- cwd: normalizedCwd,
19075
- active,
19076
- diffLineComments,
19077
- diffStyle,
19078
- file,
19079
- open: openFiles.includes(reviewFileValue(file)),
19080
- stuck: stickyReviewFileValue === reviewFileValue(file),
19081
- onAddDiffLineComment,
19082
- onOpenFile
19083
- }, reviewFileValue(file)))
19084
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19085
- className: "px-3 py-2.5",
19086
- children: "Working tree clean."
19087
- })
19205
+ children: !normalizedCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19206
+ className: "px-3 py-2.5",
19207
+ children: "No directory selected."
19208
+ }) : !viewerContextId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19209
+ className: "px-3 py-2.5",
19210
+ children: "Waiting for viewer context…"
19211
+ }) : filesQuery.isPending && typeof files === "undefined" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, {
19212
+ className: "px-3 py-2.5",
19213
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading changes…"]
19214
+ }) : filesQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19215
+ tone: "destructive",
19216
+ className: "px-3 py-2.5",
19217
+ children: getErrorMessage(filesQuery.error, "Failed to load changes")
19218
+ }) : files === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19219
+ className: "px-3 py-2.5",
19220
+ children: "No git repository detected."
19221
+ }) : changedFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Accordion$1, {
19222
+ multiple: true,
19223
+ value: openFiles,
19224
+ onValueChange: (nextOpenFiles) => {
19225
+ dispatch({
19226
+ type: "openFilesChanged",
19227
+ openFiles: nextOpenFiles
19228
+ });
19229
+ },
19230
+ className: "border-b border-border/80",
19231
+ children: changedFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewFileAccordionItem, {
19232
+ viewerContextId,
19233
+ cwd: normalizedCwd,
19234
+ active,
19235
+ diffLineComments,
19236
+ diffStyle,
19237
+ file,
19238
+ open: openFiles.includes(reviewFileValue(file)),
19239
+ stuck: stickyReviewFileValue === reviewFileValue(file),
19240
+ onAddDiffLineComment,
19241
+ onOpenFile
19242
+ }, reviewFileValue(file)))
19243
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19244
+ className: "px-3 py-2.5",
19245
+ children: "Working tree clean."
19246
+ })
19247
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
19248
+ bottomHeight: changesScrollBottomGradientHeight,
19249
+ topHeight: changesScrollTopGradientHeight
19250
+ })]
19088
19251
  }),
19089
19252
  showEmbeddedHistory && normalizedCwd && viewerContextId && files !== null && !filesQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19090
19253
  ref: historyPanelRef,
@@ -19235,6 +19398,7 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19235
19398
  } : {};
19236
19399
  const themeOptions = usePicoDiffThemeOptions();
19237
19400
  const canStage = gitFileCanStage(file);
19401
+ const canUnstage = gitFileCanUnstage(file);
19238
19402
  const invalidateChangedFileQueries = async () => {
19239
19403
  await invalidateGitQueries({
19240
19404
  queryClient,
@@ -19243,26 +19407,27 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19243
19407
  });
19244
19408
  };
19245
19409
  const stageMutation = useMutation({
19246
- mutationFn: async () => await fetchJson(buildRequestUrl("/api/git-stage", { contextId: viewerContextId }), {
19410
+ mutationFn: async (action) => await fetchJson(buildRequestUrl("/api/git-stage", { contextId: viewerContextId }), {
19247
19411
  method: "POST",
19248
19412
  headers: { "content-type": "application/json" },
19249
19413
  body: JSON.stringify({
19414
+ action,
19250
19415
  cwd: normalizedCwd,
19251
19416
  path: file.path,
19252
19417
  previousPath: file.previousPath
19253
19418
  })
19254
19419
  }),
19255
- onSuccess: async () => {
19420
+ onSuccess: async (_response, action) => {
19256
19421
  await queryClient.invalidateQueries({
19257
19422
  queryKey: picoQueryKeys.gitStatus(viewerContextId, normalizedCwd),
19258
19423
  exact: true,
19259
19424
  refetchType: "active"
19260
19425
  });
19261
19426
  await invalidateChangedFileQueries();
19262
- toast.success("Staged changes", { description: file.path });
19427
+ toast.success(action === "unstage" ? "Unstaged changes" : "Staged changes", { description: file.path });
19263
19428
  },
19264
- onError: (error) => {
19265
- toast.error(getErrorMessage(error, "Failed to stage changes"));
19429
+ onError: (error, action) => {
19430
+ toast.error(getErrorMessage(error, action === "unstage" ? "Failed to unstage changes" : "Failed to stage changes"));
19266
19431
  }
19267
19432
  });
19268
19433
  const discardMutation = useMutation({
@@ -19299,6 +19464,9 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19299
19464
  oldContent,
19300
19465
  newContent
19301
19466
  ]);
19467
+ const gitActionDisabled = actionPending || !viewerContextId || !normalizedCwd;
19468
+ const stagePending = stageMutation.isPending && stageMutation.variables === "stage";
19469
+ const unstagePending = stageMutation.isPending && stageMutation.variables === "unstage";
19302
19470
  const openFile = (event) => {
19303
19471
  event?.stopPropagation();
19304
19472
  onOpenFile?.(file.path, { pin: true });
@@ -19309,50 +19477,81 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComment
19309
19477
  };
19310
19478
  const stageFileChanges = (event) => {
19311
19479
  event?.stopPropagation();
19312
- stageMutation.mutate();
19480
+ stageMutation.mutate("stage");
19481
+ };
19482
+ const unstageFileChanges = (event) => {
19483
+ event?.stopPropagation();
19484
+ stageMutation.mutate("unstage");
19313
19485
  };
19486
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionTrigger$1, {
19487
+ "data-review-file-trigger": true,
19488
+ "data-review-file-value": value,
19489
+ 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",
19490
+ children: [
19491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19492
+ className: "grid min-w-0 flex-1 grid-cols-[auto_auto_minmax(0,1fr)_auto] items-center gap-2",
19493
+ children: [
19494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileStatus, { status: file.status }),
19495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileTypeIcon, { path: file.path }),
19496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19497
+ className: "min-w-0 truncate text-left",
19498
+ children: file.previousPath ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
19499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19500
+ className: "text-muted-foreground",
19501
+ children: file.previousPath
19502
+ }),
19503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19504
+ className: "text-muted-foreground/70",
19505
+ children: " → "
19506
+ }),
19507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: file.path })
19508
+ ] }) : file.path
19509
+ }),
19510
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileDiff, { file })
19511
+ ]
19512
+ }),
19513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
19514
+ "data-slot": "accordion-trigger-icon",
19515
+ className: "pointer-events-none size-4 shrink-0 group-aria-expanded/review-file-trigger:hidden"
19516
+ }),
19517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {
19518
+ "data-slot": "accordion-trigger-icon",
19519
+ className: "pointer-events-none hidden size-4 shrink-0 group-aria-expanded/review-file-trigger:inline"
19520
+ })
19521
+ ]
19522
+ });
19314
19523
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionItem, {
19315
19524
  value,
19316
19525
  className: "border-border/70",
19317
19526
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionHeader, {
19318
19527
  className: cn("group/review-file-header sticky top-0 z-20 flex bg-background transition-shadow", stuck && "shadow-sm"),
19319
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionTrigger$1, {
19320
- "data-review-file-trigger": true,
19321
- "data-review-file-value": value,
19322
- 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",
19528
+ 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, {
19529
+ className: "w-48",
19323
19530
  children: [
19324
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19325
- className: "grid min-w-0 flex-1 grid-cols-[auto_auto_minmax(0,1fr)_auto] items-center gap-2",
19326
- children: [
19327
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileStatus, { status: file.status }),
19328
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileTypeIcon, { path: file.path }),
19329
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19330
- className: "min-w-0 truncate text-left",
19331
- children: file.previousPath ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
19332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19333
- className: "text-muted-foreground",
19334
- children: file.previousPath
19335
- }),
19336
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19337
- className: "text-muted-foreground/70",
19338
- children: " → "
19339
- }),
19340
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: file.path })
19341
- ] }) : file.path
19342
- }),
19343
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitFileDiff, { file })
19344
- ]
19531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19532
+ disabled: !onOpenFile,
19533
+ onClick: openFile,
19534
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowUpRight, {}), "Open file"]
19345
19535
  }),
19346
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
19347
- "data-slot": "accordion-trigger-icon",
19348
- className: "pointer-events-none size-4 shrink-0 group-aria-expanded/review-file-trigger:hidden"
19536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextMenuSeparator, {}),
19537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19538
+ disabled: gitActionDisabled || !canStage,
19539
+ onClick: stageFileChanges,
19540
+ children: [stagePending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "Stage changes"]
19349
19541
  }),
19350
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {
19351
- "data-slot": "accordion-trigger-icon",
19352
- className: "pointer-events-none hidden size-4 shrink-0 group-aria-expanded/review-file-trigger:inline"
19542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19543
+ disabled: gitActionDisabled || !canUnstage,
19544
+ onClick: unstageFileChanges,
19545
+ children: [unstagePending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, {}), "Unstage changes"]
19546
+ }),
19547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContextMenuItem, {
19548
+ variant: "destructive",
19549
+ disabled: gitActionDisabled,
19550
+ onClick: discardFileChanges,
19551
+ children: [discardMutation.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, {}), "Discard changes"]
19353
19552
  })
19354
19553
  ]
19355
- })
19554
+ })] })
19356
19555
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionContent, {
19357
19556
  className: "bg-background p-0",
19358
19557
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -19597,7 +19796,7 @@ function assignClassName(element, className) {
19597
19796
  }
19598
19797
  function renderPendingCommitDiffLineComment(pendingComment) {
19599
19798
  const form = document.createElement("form");
19600
- 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");
19799
+ assignClassName(form, "m-2 w-[calc(100%_-_1rem)] max-w-[38rem] rounded-lg border border-border/70 bg-background p-2 shadow-sm");
19601
19800
  const header = document.createElement("div");
19602
19801
  assignClassName(header, "mb-2 flex min-w-0 items-center justify-between gap-2 text-xs text-muted-foreground");
19603
19802
  const label = document.createElement("span");
@@ -19740,7 +19939,7 @@ function CommitFileDiffAccordionItem({ comments, diffStyle, fileDiff, index, onS
19740
19939
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionTrigger$1, {
19741
19940
  "data-commit-file-trigger": true,
19742
19941
  "data-commit-file-value": value,
19743
- 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",
19942
+ 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",
19744
19943
  children: [
19745
19944
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19746
19945
  className: "grid min-w-0 flex-1 grid-cols-[minmax(0,1fr)_auto] items-baseline gap-3",
@@ -19878,8 +20077,14 @@ function CommitDiffHeaderTitle({ tab }) {
19878
20077
  function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onAddDiffLineComment, tab }) {
19879
20078
  const [diffStyle, setDiffStyle] = import_react.useState("unified");
19880
20079
  const [stickyCommitFileValue, setStickyCommitFileValue] = import_react.useState("");
20080
+ const isMobile = useIsMobile();
19881
20081
  const [commentTarget, setCommentTarget] = import_react.useState(null);
19882
20082
  const diffScrollRef = import_react.useRef(null);
20083
+ const { bottomHeight: diffScrollBottomGradientHeight, onScroll: onDiffScrollGradientScroll, setScrollElement: setDiffScrollGradientElement, topHeight: diffScrollTopGradientHeight } = useScrollGradients();
20084
+ const setDiffScrollElement = import_react.useCallback((element) => {
20085
+ diffScrollRef.current = element;
20086
+ setDiffScrollGradientElement(element);
20087
+ }, [setDiffScrollGradientElement]);
19883
20088
  const updateStickyCommitFileHeader = (scrollElement) => {
19884
20089
  setDerivedScrollState(setStickyCommitFileValue, getStuckScrollTriggerValue({
19885
20090
  getValue: (trigger) => trigger.dataset.commitFileValue || "",
@@ -19945,6 +20150,7 @@ function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onA
19945
20150
  className: "flex min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/70 bg-background p-2",
19946
20151
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffHeaderTitle, { tab }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ToggleGroup$1, {
19947
20152
  variant: "outline",
20153
+ size: isMobile ? "default" : "sm",
19948
20154
  value: [diffStyle],
19949
20155
  onValueChange: (values) => {
19950
20156
  const value = values[0];
@@ -19958,41 +20164,48 @@ function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onA
19958
20164
  children: "Split"
19959
20165
  })]
19960
20166
  })]
19961
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19962
- ref: diffScrollRef,
19963
- className: "min-h-0 flex-1 overflow-auto",
19964
- onScroll: (event) => {
19965
- updateStickyCommitFileHeader(event.currentTarget);
19966
- },
19967
- children: diffQuery.isPending && !diffQuery.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19968
- className: "p-4",
19969
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading commit diff…"] })
19970
- }) : diffQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19971
- className: "p-4",
19972
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
19973
- tone: "destructive",
19974
- children: getErrorMessage(diffQuery.error, "Failed to load commit diff")
20167
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20168
+ className: "relative min-h-0 flex-1",
20169
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20170
+ ref: setDiffScrollElement,
20171
+ className: "h-full overflow-auto",
20172
+ onScroll: (event) => {
20173
+ updateStickyCommitFileHeader(event.currentTarget);
20174
+ onDiffScrollGradientScroll(event);
20175
+ },
20176
+ children: diffQuery.isPending && !diffQuery.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20177
+ className: "p-4",
20178
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(GitSectionNote, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), " Loading commit diff…"] })
20179
+ }) : diffQuery.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20180
+ className: "p-4",
20181
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
20182
+ tone: "destructive",
20183
+ children: getErrorMessage(diffQuery.error, "Failed to load commit diff")
20184
+ })
20185
+ }) : diffQuery.data?.patch && tab.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitSingleFileDiff, {
20186
+ comments: commentsForTab.filter((comment) => {
20187
+ if (!tab.path) return true;
20188
+ return comment.path === tab.path || comment.path === tab.previousPath;
20189
+ }),
20190
+ diffStyle,
20191
+ onStartComment: startLineComment,
20192
+ patch: diffQuery.data.patch,
20193
+ pendingComment
20194
+ }) : diffQuery.data?.patch ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitPatchDiffs, {
20195
+ comments: commentsForTab,
20196
+ diffStyle,
20197
+ onStartComment: startLineComment,
20198
+ patch: diffQuery.data.patch,
20199
+ pendingComment,
20200
+ stuckFileValue: stickyCommitFileValue
20201
+ }, `${diffQuery.data.commit}:${diffQuery.data.mode}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20202
+ className: "p-4",
20203
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, { children: "No line changes." })
19975
20204
  })
19976
- }) : diffQuery.data?.patch && tab.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitSingleFileDiff, {
19977
- comments: commentsForTab.filter((comment) => {
19978
- if (!tab.path) return true;
19979
- return comment.path === tab.path || comment.path === tab.previousPath;
19980
- }),
19981
- diffStyle,
19982
- onStartComment: startLineComment,
19983
- patch: diffQuery.data.patch,
19984
- pendingComment
19985
- }) : diffQuery.data?.patch ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitPatchDiffs, {
19986
- comments: commentsForTab,
19987
- diffStyle,
19988
- onStartComment: startLineComment,
19989
- patch: diffQuery.data.patch,
19990
- pendingComment,
19991
- stuckFileValue: stickyCommitFileValue
19992
- }, `${diffQuery.data.commit}:${diffQuery.data.mode}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19993
- className: "p-4",
19994
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, { children: "No line changes." })
19995
- })
20205
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollGradientOverlays, {
20206
+ bottomHeight: diffScrollBottomGradientHeight,
20207
+ topHeight: diffScrollTopGradientHeight
20208
+ })]
19996
20209
  })]
19997
20210
  });
19998
20211
  }
@@ -20249,43 +20462,848 @@ function RightSidebar({ viewerContextId, cwd, active, activeFilePath = "", activ
20249
20462
  })]
20250
20463
  });
20251
20464
  }
20252
- function readStoredRightSidebarActiveTab() {
20253
- const stored = safeLocalStorageGetItem(RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY);
20254
- return stored === "files" || stored === "review" ? stored : "review";
20255
- }
20256
- function storeRightSidebarActiveTab(tab) {
20257
- if (tab === "commit-diff" || tab === "history") return;
20258
- safeLocalStorageSetItem(RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, tab);
20465
+ var TERMINAL_ANSI_THEMES = {
20466
+ dark: {
20467
+ black: "#484f58",
20468
+ red: "#ff7b72",
20469
+ green: "#3fb950",
20470
+ yellow: "#d29922",
20471
+ blue: "#58a6ff",
20472
+ magenta: "#bc8cff",
20473
+ cyan: "#39c5cf",
20474
+ white: "#b1bac4",
20475
+ brightBlack: "#6e7681",
20476
+ brightRed: "#ffa198",
20477
+ brightGreen: "#56d364",
20478
+ brightYellow: "#e3b341",
20479
+ brightBlue: "#79c0ff",
20480
+ brightMagenta: "#d2a8ff",
20481
+ brightCyan: "#56d4dd",
20482
+ brightWhite: "#f0f6fc"
20483
+ },
20484
+ light: {
20485
+ black: "#24292f",
20486
+ red: "#cf222e",
20487
+ green: "#116329",
20488
+ yellow: "#953800",
20489
+ blue: "#0969da",
20490
+ magenta: "#8250df",
20491
+ cyan: "#1b7c83",
20492
+ white: "#6e7781",
20493
+ brightBlack: "#57606a",
20494
+ brightRed: "#a40e26",
20495
+ brightGreen: "#1a7f37",
20496
+ brightYellow: "#9a6700",
20497
+ brightBlue: "#218bff",
20498
+ brightMagenta: "#a475f9",
20499
+ brightCyan: "#3192aa",
20500
+ brightWhite: "#24292f"
20501
+ }
20502
+ };
20503
+ var terminalTabCounter = 0;
20504
+ function createTerminalTabId() {
20505
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `terminal-tab-${crypto.randomUUID()}`;
20506
+ terminalTabCounter += 1;
20507
+ return `terminal-tab-${Date.now()}-${terminalTabCounter}`;
20259
20508
  }
20260
- function setRightSidebarActiveTab(store, tab) {
20261
- setStoreField(store, "activeTab", tab);
20262
- storeRightSidebarActiveTab(tab);
20509
+ function createTerminalTab(id = createTerminalTabId()) {
20510
+ return {
20511
+ id,
20512
+ status: { type: "idle" },
20513
+ terminalId: null
20514
+ };
20263
20515
  }
20264
- function createInitialRightSidebarState() {
20516
+ function createTerminalTabsState(scopeKey) {
20517
+ const storedTabs = readStoredTerminalTabs(scopeKey);
20518
+ const tabs = storedTabs.tabs.map((id) => createTerminalTab(id));
20265
20519
  return {
20266
- activeTab: readStoredRightSidebarActiveTab(),
20267
- fileActivePath: "",
20268
- filePreviewPath: "",
20269
- fileTabs: [],
20270
- fileTreeCollapsed: false
20520
+ activeTabId: storedTabs.activeTabId ?? tabs[0]?.id ?? null,
20521
+ scopeKey,
20522
+ tabs
20271
20523
  };
20272
20524
  }
20273
- function selectRightSidebarVisibleFileTabs(state) {
20274
- return state.filePreviewPath && !state.fileTabs.includes(state.filePreviewPath) ? [...state.fileTabs, state.filePreviewPath] : state.fileTabs;
20525
+ function rememberTerminalTabsState(state) {
20526
+ rememberStoredTerminalTabs(state.scopeKey, {
20527
+ activeTabId: state.activeTabId,
20528
+ tabs: state.tabs.map((tab) => tab.id)
20529
+ });
20275
20530
  }
20276
- function selectRightSidebarHasVisibleFiles(state) {
20277
- return selectRightSidebarVisibleFileTabs(state).length > 0;
20531
+ function terminalStatusText(status) {
20532
+ switch (status.type) {
20533
+ case "idle": return "Idle";
20534
+ case "connecting": return "Connecting…";
20535
+ case "connected": return status.shell ? `Running ${status.shell}` : "Connected";
20536
+ case "exited": return `Exited ${status.exitCode}`;
20537
+ case "error": return "Terminal error";
20538
+ }
20278
20539
  }
20279
- function resetRightSidebarFiles(store) {
20280
- setStoreState(store, (state) => ({
20281
- ...state,
20282
- fileActivePath: "",
20283
- filePreviewPath: "",
20284
- fileTabs: [],
20285
- fileTreeCollapsed: false
20286
- }));
20540
+ function terminalStatusDetail(status) {
20541
+ switch (status.type) {
20542
+ case "idle": return "This terminal has not started yet.";
20543
+ case "connecting": return "Pico is connecting to the terminal process.";
20544
+ case "connected": return status.shell ? `The terminal is connected and running ${status.shell}.` : "The terminal is connected.";
20545
+ 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}.`;
20546
+ case "error": return status.message;
20547
+ }
20287
20548
  }
20288
- function openRightSidebarFile(store, path, options) {
20549
+ function terminalTabLabel(tab, index) {
20550
+ if (tab.status.type === "connected" && tab.status.shell) return `${index + 1}: ${tab.status.shell}`;
20551
+ return `Terminal ${index + 1}`;
20552
+ }
20553
+ function statusDotClassName(status) {
20554
+ switch (status.type) {
20555
+ case "connected": return "bg-success";
20556
+ case "connecting": return "bg-warning";
20557
+ case "error":
20558
+ case "exited": return "bg-destructive";
20559
+ case "idle": return "bg-muted-foreground/50";
20560
+ }
20561
+ }
20562
+ function cssVariable(name, fallback) {
20563
+ return window.getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
20564
+ }
20565
+ function resolveTerminalThemeMode() {
20566
+ for (const className of document.documentElement.classList) {
20567
+ const mode = appliedThemeClassColorMode(className);
20568
+ if (mode) return mode;
20569
+ }
20570
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
20571
+ }
20572
+ function terminalAnsiTheme() {
20573
+ const fallback = TERMINAL_ANSI_THEMES[resolveTerminalThemeMode()];
20574
+ return {
20575
+ black: cssVariable("--terminal-ansi-black", fallback.black),
20576
+ red: cssVariable("--terminal-ansi-red", fallback.red),
20577
+ green: cssVariable("--terminal-ansi-green", fallback.green),
20578
+ yellow: cssVariable("--terminal-ansi-yellow", fallback.yellow),
20579
+ blue: cssVariable("--terminal-ansi-blue", fallback.blue),
20580
+ magenta: cssVariable("--terminal-ansi-magenta", fallback.magenta),
20581
+ cyan: cssVariable("--terminal-ansi-cyan", fallback.cyan),
20582
+ white: cssVariable("--terminal-ansi-white", fallback.white),
20583
+ brightBlack: cssVariable("--terminal-ansi-bright-black", fallback.brightBlack),
20584
+ brightRed: cssVariable("--terminal-ansi-bright-red", fallback.brightRed),
20585
+ brightGreen: cssVariable("--terminal-ansi-bright-green", fallback.brightGreen),
20586
+ brightYellow: cssVariable("--terminal-ansi-bright-yellow", fallback.brightYellow),
20587
+ brightBlue: cssVariable("--terminal-ansi-bright-blue", fallback.brightBlue),
20588
+ brightMagenta: cssVariable("--terminal-ansi-bright-magenta", fallback.brightMagenta),
20589
+ brightCyan: cssVariable("--terminal-ansi-bright-cyan", fallback.brightCyan),
20590
+ brightWhite: cssVariable("--terminal-ansi-bright-white", fallback.brightWhite)
20591
+ };
20592
+ }
20593
+ function createTerminalTheme() {
20594
+ const ansiTheme = terminalAnsiTheme();
20595
+ return {
20596
+ ...ansiTheme,
20597
+ background: cssVariable("--terminal-background", cssVariable("--background", "#111111")),
20598
+ cursor: cssVariable("--terminal-cursor", cssVariable("--primary", "#7aa2f7")),
20599
+ foreground: cssVariable("--terminal-foreground", cssVariable("--foreground", ansiTheme.brightWhite)),
20600
+ selectionBackground: cssVariable("--terminal-selection-background", cssVariable("--code-selection", "#334155"))
20601
+ };
20602
+ }
20603
+ function createTerminalFontFamily() {
20604
+ return cssVariable("--font-mono", "Menlo, ui-monospace, SFMono-Regular, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace");
20605
+ }
20606
+ function errorMessage(error) {
20607
+ if (error instanceof Error && error.message.trim()) return error.message;
20608
+ if (typeof error === "string" && error.trim()) return error;
20609
+ return "Unknown terminal error";
20610
+ }
20611
+ function buildTerminalWebSocketUrl(path, options) {
20612
+ const url = new URL(buildRequestUrl(path, options));
20613
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
20614
+ return url.toString();
20615
+ }
20616
+ function exitedStatus(event) {
20617
+ return typeof event.signal === "number" && event.signal > 0 ? {
20618
+ type: "exited",
20619
+ exitCode: event.exitCode,
20620
+ signal: event.signal
20621
+ } : {
20622
+ type: "exited",
20623
+ exitCode: event.exitCode
20624
+ };
20625
+ }
20626
+ function isTerminalToggleEvent(event) {
20627
+ return event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && (event.key === "`" || event.code === "Backquote");
20628
+ }
20629
+ function TerminalStatusDot({ status }) {
20630
+ if (status.type === "connecting") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
20631
+ size: "xs",
20632
+ className: "shrink-0"
20633
+ });
20634
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20635
+ "aria-hidden": "true",
20636
+ className: cn("size-2 shrink-0 rounded-full", statusDotClassName(status))
20637
+ });
20638
+ }
20639
+ function TerminalStatusDialog({ cwd, index, onOpenChange, open, tab }) {
20640
+ const status = tab?.status ?? { type: "idle" };
20641
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
20642
+ open,
20643
+ onOpenChange,
20644
+ focusPromptOnClose: false,
20645
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
20646
+ className: "max-w-sm",
20647
+ children: [
20648
+ /* @__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) })] }),
20649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
20650
+ className: "grid gap-3 text-sm",
20651
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20652
+ className: "grid gap-1",
20653
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
20654
+ className: "text-xs font-medium text-muted-foreground",
20655
+ children: "Status"
20656
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: terminalStatusText(status) })]
20657
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20658
+ className: "grid gap-1",
20659
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
20660
+ className: "text-xs font-medium text-muted-foreground",
20661
+ children: "Working directory"
20662
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
20663
+ className: "break-all",
20664
+ children: cwd
20665
+ })]
20666
+ })]
20667
+ }),
20668
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, { showCloseButton: true })
20669
+ ]
20670
+ })
20671
+ });
20672
+ }
20673
+ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, selected, sessionId, tabId, viewerContextId }) {
20674
+ const terminalElementRef = import_react.useRef(null);
20675
+ const terminalRef = import_react.useRef(null);
20676
+ const terminalIdRef = import_react.useRef(null);
20677
+ const activeRef = import_react.useRef(active);
20678
+ const fitAndResizeRef = import_react.useRef(() => {});
20679
+ const onStatusChangeRef = import_react.useRef(onStatusChange);
20680
+ const onTerminalIdChangeRef = import_react.useRef(onTerminalIdChange);
20681
+ const [status, setStatus] = import_react.useState({ type: "idle" });
20682
+ import_react.useLayoutEffect(() => {
20683
+ activeRef.current = active;
20684
+ }, [active]);
20685
+ import_react.useEffect(() => {
20686
+ onStatusChangeRef.current = onStatusChange;
20687
+ }, [onStatusChange]);
20688
+ import_react.useEffect(() => {
20689
+ onTerminalIdChangeRef.current = onTerminalIdChange;
20690
+ }, [onTerminalIdChange]);
20691
+ import_react.useEffect(() => {
20692
+ if (!cwd) {
20693
+ setStatus({ type: "idle" });
20694
+ onStatusChangeRef.current(tabId, { type: "idle" });
20695
+ return;
20696
+ }
20697
+ const terminalElement = terminalElementRef.current;
20698
+ if (!terminalElement) return;
20699
+ let disposed = false;
20700
+ let websocket = null;
20701
+ let inputDisposable = null;
20702
+ let fitAddon = null;
20703
+ let resizeObserver = null;
20704
+ let inputFlushTimer = null;
20705
+ let pingTimer = null;
20706
+ let reconnectTimer = null;
20707
+ let resizeFrame = null;
20708
+ let inputBuffer = "";
20709
+ let lastResizeKey = "";
20710
+ let lastOutputSeq = 0;
20711
+ let nextInputSeq = 1;
20712
+ let reconnectAttempt = 0;
20713
+ let terminalExited = false;
20714
+ let terminalTransportReady = false;
20715
+ const pendingInputs = /* @__PURE__ */ new Map();
20716
+ const requestOptions = sessionId ? {
20717
+ contextId: viewerContextId,
20718
+ sessionId
20719
+ } : { contextId: viewerContextId };
20720
+ const publishStatus = (nextStatus) => {
20721
+ if (disposed) return;
20722
+ setStatus(nextStatus);
20723
+ onStatusChangeRef.current(tabId, nextStatus);
20724
+ };
20725
+ const postJson = async (path, body) => {
20726
+ return await fetchJson(buildRequestUrl(path, requestOptions), {
20727
+ body: JSON.stringify(body),
20728
+ headers: { "content-type": "application/json" },
20729
+ method: "POST"
20730
+ });
20731
+ };
20732
+ const sendTerminalMessage = (message) => {
20733
+ if (!websocket || websocket.readyState !== WebSocket.OPEN) return false;
20734
+ try {
20735
+ websocket.send(JSON.stringify(message));
20736
+ return true;
20737
+ } catch (error) {
20738
+ publishStatus({
20739
+ type: "error",
20740
+ message: errorMessage(error)
20741
+ });
20742
+ return false;
20743
+ }
20744
+ };
20745
+ const sendPendingInputs = () => {
20746
+ if (!terminalTransportReady) return;
20747
+ for (const [inputSeq, data] of [...pendingInputs.entries()].sort(([left], [right]) => left - right)) if (!sendTerminalMessage({
20748
+ type: "input",
20749
+ data,
20750
+ inputSeq
20751
+ })) return;
20752
+ };
20753
+ const flushInput = () => {
20754
+ inputFlushTimer = null;
20755
+ if (!terminalIdRef.current || !inputBuffer || !terminalTransportReady) return;
20756
+ const data = inputBuffer;
20757
+ const inputSeq = nextInputSeq;
20758
+ nextInputSeq += 1;
20759
+ inputBuffer = "";
20760
+ pendingInputs.set(inputSeq, data);
20761
+ sendPendingInputs();
20762
+ };
20763
+ const scheduleInputFlush = () => {
20764
+ if (inputFlushTimer !== null) return;
20765
+ inputFlushTimer = window.setTimeout(flushInput, 12);
20766
+ };
20767
+ const fitAndResize = () => {
20768
+ const terminal = terminalRef.current;
20769
+ if (!terminal || !fitAddon) return;
20770
+ try {
20771
+ fitAddon.fit();
20772
+ } catch {
20773
+ return;
20774
+ }
20775
+ if (!terminalIdRef.current || !terminalTransportReady) return;
20776
+ const resizeKey = `${terminal.cols}x${terminal.rows}`;
20777
+ if (resizeKey === lastResizeKey) return;
20778
+ if (!sendTerminalMessage({
20779
+ type: "resize",
20780
+ cols: terminal.cols,
20781
+ rows: terminal.rows
20782
+ })) return;
20783
+ lastResizeKey = resizeKey;
20784
+ };
20785
+ const scheduleFitAndResize = () => {
20786
+ if (resizeFrame !== null) return;
20787
+ resizeFrame = window.requestAnimationFrame(() => {
20788
+ resizeFrame = null;
20789
+ fitAndResize();
20790
+ });
20791
+ };
20792
+ fitAndResizeRef.current = scheduleFitAndResize;
20793
+ const clearPingTimer = () => {
20794
+ if (pingTimer === null) return;
20795
+ window.clearInterval(pingTimer);
20796
+ pingTimer = null;
20797
+ };
20798
+ const startPingTimer = () => {
20799
+ clearPingTimer();
20800
+ pingTimer = window.setInterval(() => {
20801
+ sendTerminalMessage({ type: "ping" });
20802
+ }, 2e4);
20803
+ };
20804
+ const scheduleWebSocketReconnect = () => {
20805
+ if (disposed || terminalExited || reconnectTimer !== null) return;
20806
+ terminalTransportReady = false;
20807
+ clearPingTimer();
20808
+ reconnectAttempt += 1;
20809
+ const delay = Math.min(1e4, 500 * 2 ** (reconnectAttempt - 1));
20810
+ reconnectTimer = window.setTimeout(() => {
20811
+ reconnectTimer = null;
20812
+ const id = terminalIdRef.current;
20813
+ if (!id || disposed || terminalExited) return;
20814
+ publishStatus({ type: "connecting" });
20815
+ connectTerminalWebSocket(id);
20816
+ }, delay);
20817
+ };
20818
+ const handleTerminalEvent = (event) => {
20819
+ const terminal = terminalRef.current;
20820
+ if (!terminal) return;
20821
+ if (event.type === "output") {
20822
+ if (typeof event.seq === "number") {
20823
+ if (event.seq <= lastOutputSeq) return;
20824
+ lastOutputSeq = event.seq;
20825
+ }
20826
+ terminal.write(event.data);
20827
+ return;
20828
+ }
20829
+ if (event.type === "reset") {
20830
+ lastOutputSeq = Math.max(0, event.firstSeq - 1);
20831
+ terminal.clear();
20832
+ terminal.writeln("Terminal output replay gap; showing retained output.");
20833
+ return;
20834
+ }
20835
+ if (event.type === "ready") {
20836
+ terminalTransportReady = true;
20837
+ reconnectAttempt = 0;
20838
+ nextInputSeq = Math.max(nextInputSeq, event.nextInputSeq ?? 1);
20839
+ publishStatus({
20840
+ type: "connected",
20841
+ shell: event.shell
20842
+ });
20843
+ startPingTimer();
20844
+ sendPendingInputs();
20845
+ flushInput();
20846
+ lastResizeKey = "";
20847
+ scheduleFitAndResize();
20848
+ return;
20849
+ }
20850
+ if (event.type === "input_ack") {
20851
+ pendingInputs.delete(event.inputSeq);
20852
+ return;
20853
+ }
20854
+ if (event.type === "pong") return;
20855
+ if (event.type === "exit") {
20856
+ terminalExited = true;
20857
+ terminalTransportReady = false;
20858
+ clearPingTimer();
20859
+ publishStatus(exitedStatus(event));
20860
+ return;
20861
+ }
20862
+ if (event.type === "error") publishStatus({
20863
+ type: "error",
20864
+ message: event.error
20865
+ });
20866
+ };
20867
+ function connectTerminalWebSocket(id) {
20868
+ if (websocket) {
20869
+ websocket.onclose = null;
20870
+ websocket.onerror = null;
20871
+ websocket.onmessage = null;
20872
+ websocket.close();
20873
+ }
20874
+ terminalTransportReady = false;
20875
+ const nextWebSocket = new WebSocket(buildTerminalWebSocketUrl(`/api/terminal/${encodeURIComponent(id)}/ws`, {
20876
+ ...requestOptions,
20877
+ searchParams: { lastSeq: lastOutputSeq || void 0 }
20878
+ }));
20879
+ websocket = nextWebSocket;
20880
+ nextWebSocket.onmessage = (message) => {
20881
+ if (disposed || websocket !== nextWebSocket) return;
20882
+ try {
20883
+ handleTerminalEvent(JSON.parse(message.data));
20884
+ } catch (error) {
20885
+ publishStatus({
20886
+ type: "error",
20887
+ message: errorMessage(error)
20888
+ });
20889
+ }
20890
+ };
20891
+ nextWebSocket.onclose = () => {
20892
+ if (websocket !== nextWebSocket) return;
20893
+ websocket = null;
20894
+ terminalTransportReady = false;
20895
+ scheduleWebSocketReconnect();
20896
+ };
20897
+ nextWebSocket.onerror = () => {
20898
+ if (websocket !== nextWebSocket) return;
20899
+ terminalTransportReady = false;
20900
+ nextWebSocket.close();
20901
+ };
20902
+ }
20903
+ const startTerminal = async () => {
20904
+ try {
20905
+ publishStatus({ type: "connecting" });
20906
+ terminalElement.textContent = "";
20907
+ 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)]);
20908
+ if (disposed) return;
20909
+ const terminal = new Terminal({
20910
+ allowTransparency: true,
20911
+ convertEol: false,
20912
+ cursorBlink: true,
20913
+ fontFamily: createTerminalFontFamily(),
20914
+ fontSize: 12,
20915
+ letterSpacing: 0,
20916
+ lineHeight: 1.2,
20917
+ scrollback: 5e3,
20918
+ theme: createTerminalTheme()
20919
+ });
20920
+ fitAddon = new FitAddon();
20921
+ terminal.loadAddon(fitAddon);
20922
+ terminal.open(terminalElement);
20923
+ terminalRef.current = terminal;
20924
+ inputDisposable = terminal.onData((data) => {
20925
+ if (!activeRef.current) return;
20926
+ inputBuffer += data;
20927
+ scheduleInputFlush();
20928
+ });
20929
+ scheduleFitAndResize();
20930
+ const terminalResponse = await postJson("/api/terminal", {
20931
+ clientKey: tabId,
20932
+ cols: terminal.cols,
20933
+ rows: terminal.rows
20934
+ });
20935
+ if (disposed) return;
20936
+ terminalIdRef.current = terminalResponse.id;
20937
+ onTerminalIdChangeRef.current(tabId, terminalResponse.id);
20938
+ connectTerminalWebSocket(terminalResponse.id);
20939
+ resizeObserver = new ResizeObserver(scheduleFitAndResize);
20940
+ resizeObserver.observe(terminalElement);
20941
+ scheduleFitAndResize();
20942
+ } catch (error) {
20943
+ if (disposed) return;
20944
+ const message = errorMessage(error);
20945
+ publishStatus({
20946
+ type: "error",
20947
+ message
20948
+ });
20949
+ terminalRef.current?.writeln(`\r\n${message}`);
20950
+ }
20951
+ };
20952
+ startTerminal();
20953
+ return () => {
20954
+ disposed = true;
20955
+ if (inputFlushTimer !== null) window.clearTimeout(inputFlushTimer);
20956
+ if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
20957
+ if (reconnectTimer !== null) window.clearTimeout(reconnectTimer);
20958
+ clearPingTimer();
20959
+ flushInput();
20960
+ resizeObserver?.disconnect();
20961
+ if (websocket) {
20962
+ websocket.onclose = null;
20963
+ websocket.onerror = null;
20964
+ websocket.onmessage = null;
20965
+ websocket.close();
20966
+ }
20967
+ inputDisposable?.dispose();
20968
+ terminalRef.current?.dispose();
20969
+ terminalRef.current = null;
20970
+ terminalIdRef.current = null;
20971
+ fitAndResizeRef.current = () => {};
20972
+ };
20973
+ }, [
20974
+ cwd,
20975
+ sessionId,
20976
+ tabId,
20977
+ viewerContextId
20978
+ ]);
20979
+ import_react.useEffect(() => {
20980
+ const applyTheme = () => {
20981
+ const terminal = terminalRef.current;
20982
+ if (!terminal) return;
20983
+ terminal.options.theme = createTerminalTheme();
20984
+ terminal.options.fontFamily = createTerminalFontFamily();
20985
+ };
20986
+ const observer = new MutationObserver(applyTheme);
20987
+ observer.observe(document.documentElement, {
20988
+ attributeFilter: ["class", "data-pico-theme-mode"],
20989
+ attributes: true
20990
+ });
20991
+ applyTheme();
20992
+ return () => observer.disconnect();
20993
+ }, []);
20994
+ import_react.useEffect(() => {
20995
+ if (!active) {
20996
+ terminalRef.current?.blur();
20997
+ return;
20998
+ }
20999
+ const frame = window.requestAnimationFrame(() => {
21000
+ fitAndResizeRef.current();
21001
+ terminalRef.current?.focus();
21002
+ });
21003
+ return () => window.cancelAnimationFrame(frame);
21004
+ }, [active, status]);
21005
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21006
+ "aria-hidden": !selected,
21007
+ "data-terminal-tab": tabId,
21008
+ 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"),
21009
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21010
+ ref: terminalElementRef,
21011
+ className: "h-full min-h-0 w-full"
21012
+ })
21013
+ });
21014
+ }
21015
+ function TerminalPanel({ active, className, cwd, onClose, sessionId, showCloseButton = true, viewerContextId }) {
21016
+ const normalizedCwd = cwd?.trim() || "";
21017
+ const scopeKey = `${viewerContextId}:${sessionId ?? "no-session"}:${normalizedCwd}`;
21018
+ const [tabsState, setTabsState] = import_react.useState(() => createTerminalTabsState(scopeKey));
21019
+ const [statusDialogTabId, setStatusDialogTabId] = import_react.useState(null);
21020
+ const tabs = tabsState.scopeKey === scopeKey ? tabsState.tabs : [];
21021
+ const selectedTabId = tabsState.scopeKey === scopeKey ? tabsState.activeTabId ?? tabs[0]?.id ?? null : null;
21022
+ import_react.useEffect(() => {
21023
+ if (tabsState.scopeKey === scopeKey) return;
21024
+ setTabsState(createTerminalTabsState(scopeKey));
21025
+ }, [scopeKey, tabsState.scopeKey]);
21026
+ import_react.useEffect(() => {
21027
+ if (!active || !normalizedCwd || tabs.length > 0 || tabsState.scopeKey !== scopeKey) return;
21028
+ const tab = createTerminalTab();
21029
+ const nextState = {
21030
+ activeTabId: tab.id,
21031
+ scopeKey,
21032
+ tabs: [tab]
21033
+ };
21034
+ rememberTerminalTabsState(nextState);
21035
+ setTabsState(nextState);
21036
+ }, [
21037
+ active,
21038
+ normalizedCwd,
21039
+ scopeKey,
21040
+ tabs.length,
21041
+ tabsState.scopeKey
21042
+ ]);
21043
+ const activeStatus = tabs.find((tab) => tab.id === selectedTabId)?.status ?? { type: "idle" };
21044
+ const activeError = activeStatus.type === "error" ? activeStatus.message : void 0;
21045
+ const statusDialogTab = tabs.find((tab) => tab.id === statusDialogTabId);
21046
+ const statusDialogTabIndex = statusDialogTab ? tabs.indexOf(statusDialogTab) : 0;
21047
+ const requestOptions = sessionId ? {
21048
+ contextId: viewerContextId,
21049
+ sessionId
21050
+ } : { contextId: viewerContextId };
21051
+ const addTerminalTab = () => {
21052
+ if (!normalizedCwd) return;
21053
+ const tab = createTerminalTab();
21054
+ setTabsState((current) => {
21055
+ const nextState = current.scopeKey !== scopeKey ? {
21056
+ activeTabId: tab.id,
21057
+ scopeKey,
21058
+ tabs: [tab]
21059
+ } : {
21060
+ ...current,
21061
+ activeTabId: tab.id,
21062
+ tabs: [...current.tabs, tab]
21063
+ };
21064
+ rememberTerminalTabsState(nextState);
21065
+ return nextState;
21066
+ });
21067
+ };
21068
+ const closeServerTerminal = (terminalId) => {
21069
+ if (!terminalId) return;
21070
+ fetchJson(buildRequestUrl(`/api/terminal/${encodeURIComponent(terminalId)}/close`, requestOptions), { method: "POST" }).catch(() => {});
21071
+ };
21072
+ const closeTerminalTab = (tabId) => {
21073
+ if (tabs.length <= 0) return;
21074
+ closeServerTerminal(tabs.find((tab) => tab.id === tabId)?.terminalId ?? null);
21075
+ setTabsState((current) => {
21076
+ if (current.scopeKey !== scopeKey || current.tabs.length <= 0) return current;
21077
+ const closingIndex = current.tabs.findIndex((tab) => tab.id === tabId);
21078
+ const nextTabs = current.tabs.filter((tab) => tab.id !== tabId);
21079
+ const nextActiveTabId = current.activeTabId === tabId ? nextTabs[Math.min(Math.max(closingIndex, 0), nextTabs.length - 1)]?.id ?? null : current.activeTabId;
21080
+ const nextState = {
21081
+ ...current,
21082
+ activeTabId: nextActiveTabId,
21083
+ tabs: nextTabs
21084
+ };
21085
+ rememberTerminalTabsState(nextState);
21086
+ return nextState;
21087
+ });
21088
+ };
21089
+ const updateTabStatus = (tabId, status) => {
21090
+ setTabsState((current) => {
21091
+ if (current.scopeKey !== scopeKey) return current;
21092
+ return {
21093
+ ...current,
21094
+ tabs: current.tabs.map((tab) => tab.id === tabId ? {
21095
+ ...tab,
21096
+ status
21097
+ } : tab)
21098
+ };
21099
+ });
21100
+ };
21101
+ const updateTabTerminalId = (tabId, terminalId) => {
21102
+ setTabsState((current) => {
21103
+ if (current.scopeKey !== scopeKey) return current;
21104
+ return {
21105
+ ...current,
21106
+ tabs: current.tabs.map((tab) => tab.id === tabId ? {
21107
+ ...tab,
21108
+ terminalId
21109
+ } : tab)
21110
+ };
21111
+ });
21112
+ };
21113
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
21114
+ "data-terminal-panel": "true",
21115
+ className: cn("pico-terminal-panel flex h-full min-h-0 w-full min-w-0 flex-col bg-background", className),
21116
+ onKeyDownCapture: (event) => {
21117
+ if (!isTerminalToggleEvent(event) || !onClose) return;
21118
+ event.preventDefault();
21119
+ event.stopPropagation();
21120
+ event.nativeEvent.stopImmediatePropagation();
21121
+ onClose();
21122
+ },
21123
+ children: [
21124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21125
+ className: "flex min-h-10 shrink-0 items-center gap-2 border-b border-border/70 px-2",
21126
+ children: [
21127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21128
+ className: "hidden max-w-56 min-w-0 flex-col gap-0 px-1 sm:flex",
21129
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21130
+ className: "truncate text-xs font-medium",
21131
+ children: "Terminal"
21132
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21133
+ className: "flex min-w-0 items-center gap-1 truncate text-[11px] text-muted-foreground",
21134
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalStatusDot, { status: activeStatus }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21135
+ className: "truncate",
21136
+ children: normalizedCwd ? formatDisplayPath$1(normalizedCwd) : "No cwd"
21137
+ })]
21138
+ })]
21139
+ }),
21140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21141
+ className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto py-1",
21142
+ children: [tabs.map((tab, index) => {
21143
+ const selected = tab.id === selectedTabId;
21144
+ const label = terminalTabLabel(tab, index);
21145
+ const statusText = terminalStatusText(tab.status);
21146
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21147
+ 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"),
21148
+ children: [
21149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21150
+ title: `${label} · ${statusText}`,
21151
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
21152
+ type: "button",
21153
+ className: "flex h-full w-7 shrink-0 items-center justify-center outline-hidden focus-visible:ring-1 focus-visible:ring-ring",
21154
+ "aria-label": `Show ${label} status`,
21155
+ onClick: (event) => {
21156
+ event.stopPropagation();
21157
+ setStatusDialogTabId(tab.id);
21158
+ },
21159
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalStatusDot, { status: tab.status })
21160
+ })
21161
+ }),
21162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21163
+ title: `${label} · ${statusText}`,
21164
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
21165
+ type: "button",
21166
+ 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",
21167
+ onClick: () => {
21168
+ setTabsState((current) => {
21169
+ if (current.scopeKey !== scopeKey) return current;
21170
+ const nextState = {
21171
+ ...current,
21172
+ activeTabId: tab.id
21173
+ };
21174
+ rememberTerminalTabsState(nextState);
21175
+ return nextState;
21176
+ });
21177
+ },
21178
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21179
+ className: "truncate",
21180
+ children: label
21181
+ })
21182
+ })
21183
+ }),
21184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21185
+ title: `Close ${label}`,
21186
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
21187
+ type: "button",
21188
+ 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",
21189
+ "aria-label": `Close ${label}`,
21190
+ onClick: (event) => {
21191
+ event.stopPropagation();
21192
+ closeTerminalTab(tab.id);
21193
+ },
21194
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3" })
21195
+ })
21196
+ })
21197
+ ]
21198
+ }, tab.id);
21199
+ }), normalizedCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21200
+ title: "New terminal tab",
21201
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
21202
+ size: "icon",
21203
+ variant: "ghost",
21204
+ className: "size-7 shrink-0",
21205
+ "aria-label": "New terminal tab",
21206
+ onClick: addTerminalTab,
21207
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "size-4" })
21208
+ })
21209
+ }) : null]
21210
+ }),
21211
+ activeError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21212
+ title: activeError,
21213
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21214
+ className: "hidden max-w-48 truncate text-[11px] text-destructive sm:inline",
21215
+ children: activeError
21216
+ })
21217
+ }) : null,
21218
+ onClose && showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21219
+ title: "Hide terminal",
21220
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
21221
+ size: "icon",
21222
+ variant: "ghost",
21223
+ className: "size-8 shrink-0",
21224
+ "aria-label": "Hide terminal",
21225
+ onClick: onClose,
21226
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {})
21227
+ })
21228
+ }) : null
21229
+ ]
21230
+ }),
21231
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalStatusDialog, {
21232
+ cwd: normalizedCwd,
21233
+ index: statusDialogTabIndex,
21234
+ open: statusDialogTab !== void 0,
21235
+ tab: statusDialogTab,
21236
+ onOpenChange: (open) => {
21237
+ if (!open) setStatusDialogTabId(null);
21238
+ }
21239
+ }),
21240
+ normalizedCwd ? tabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21241
+ className: "relative min-h-0 flex-1 overflow-hidden bg-background",
21242
+ children: tabs.map((tab) => {
21243
+ const selected = tab.id === selectedTabId;
21244
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalTabPane, {
21245
+ active: active && selected,
21246
+ cwd: normalizedCwd,
21247
+ onStatusChange: updateTabStatus,
21248
+ onTerminalIdChange: updateTabTerminalId,
21249
+ selected,
21250
+ sessionId,
21251
+ tabId: tab.id,
21252
+ viewerContextId
21253
+ }, tab.id);
21254
+ })
21255
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21256
+ className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-sm text-muted-foreground",
21257
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "No terminal tabs are open." }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
21258
+ size: "sm",
21259
+ variant: "outline",
21260
+ onClick: addTerminalTab,
21261
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "size-4" }), "New terminal tab"]
21262
+ })]
21263
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21264
+ className: "flex min-h-0 flex-1 items-center justify-center p-6 text-sm text-muted-foreground",
21265
+ children: "Open a session before starting a terminal."
21266
+ })
21267
+ ]
21268
+ });
21269
+ }
21270
+ function readStoredRightSidebarActiveTab() {
21271
+ const stored = safeLocalStorageGetItem(RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY);
21272
+ return stored === "files" || stored === "review" ? stored : "review";
21273
+ }
21274
+ function storeRightSidebarActiveTab(tab) {
21275
+ if (tab === "commit-diff" || tab === "history") return;
21276
+ safeLocalStorageSetItem(RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, tab);
21277
+ }
21278
+ function setRightSidebarActiveTab(store, tab) {
21279
+ setStoreField(store, "activeTab", tab);
21280
+ storeRightSidebarActiveTab(tab);
21281
+ }
21282
+ function createInitialRightSidebarState() {
21283
+ return {
21284
+ activeTab: readStoredRightSidebarActiveTab(),
21285
+ fileActivePath: "",
21286
+ filePreviewPath: "",
21287
+ fileTabs: [],
21288
+ fileTreeCollapsed: false
21289
+ };
21290
+ }
21291
+ function selectRightSidebarVisibleFileTabs(state) {
21292
+ return state.filePreviewPath && !state.fileTabs.includes(state.filePreviewPath) ? [...state.fileTabs, state.filePreviewPath] : state.fileTabs;
21293
+ }
21294
+ function selectRightSidebarHasVisibleFiles(state) {
21295
+ return selectRightSidebarVisibleFileTabs(state).length > 0;
21296
+ }
21297
+ function resetRightSidebarFiles(store) {
21298
+ setStoreState(store, (state) => ({
21299
+ ...state,
21300
+ fileActivePath: "",
21301
+ filePreviewPath: "",
21302
+ fileTabs: [],
21303
+ fileTreeCollapsed: false
21304
+ }));
21305
+ }
21306
+ function openRightSidebarFile(store, path, options) {
20289
21307
  if (!path) return;
20290
21308
  storeRightSidebarActiveTab("files");
20291
21309
  setStoreState(store, (state) => {
@@ -20363,22 +21381,33 @@ function reorderRightSidebarFiles(store, paths) {
20363
21381
  fileTabs: uniquePaths
20364
21382
  }));
20365
21383
  }
21384
+ function shallowTerminalSession(left, right) {
21385
+ return left.cwd === right.cwd && left.sessionId === right.sessionId;
21386
+ }
20366
21387
  function AppShellTabsList({ sessionStore, viewerContextId }) {
20367
21388
  const cwd = useSelector(sessionStore, (sessionState) => sessionState.cwd);
20368
21389
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsList, {
20369
21390
  className: "w-full gap-2 rounded-none border-b border-border/70 bg-background p-2 group-data-horizontal/tabs:h-auto md:hidden",
20370
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
20371
- value: "session",
20372
- className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
20373
- children: "Session"
20374
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
20375
- value: "git",
20376
- className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
20377
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitTabStatusText, {
20378
- viewerContextId,
20379
- cwd
21391
+ children: [
21392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
21393
+ value: "session",
21394
+ className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
21395
+ children: "Session"
21396
+ }),
21397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
21398
+ value: "git",
21399
+ className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
21400
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitTabStatusText, {
21401
+ viewerContextId,
21402
+ cwd
21403
+ })
21404
+ }),
21405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
21406
+ value: "terminal",
21407
+ className: "h-8 data-active:bg-muted dark:data-active:bg-muted",
21408
+ children: "Terminal"
20380
21409
  })
20381
- })]
21410
+ ]
20382
21411
  });
20383
21412
  }
20384
21413
  var AppShellGitPanelController = import_react.memo(function AppShellGitPanelController({ active, composerDiffLineComments, onAddDiffLineComment, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onOpenFile, onReorderFiles, rightSidebarStore, sessionStore, viewerContextId }) {
@@ -20457,6 +21486,10 @@ var DESKTOP_DEFAULT_GIT_PANEL_WIDTH = 320;
20457
21486
  var DESKTOP_MIN_SESSION_WIDTH = 320;
20458
21487
  var DESKTOP_MIN_SIDE_PANEL_WIDTH = 260;
20459
21488
  var DESKTOP_MAX_STORED_SIDE_PANEL_WIDTH = 1600;
21489
+ var DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT = 280;
21490
+ var DESKTOP_MIN_TERMINAL_PANEL_HEIGHT = 160;
21491
+ var DESKTOP_MIN_TOP_PANEL_HEIGHT = 220;
21492
+ var DESKTOP_MAX_STORED_TERMINAL_PANEL_HEIGHT = 1200;
20460
21493
  function defaultDesktopSidePanelWidths() {
20461
21494
  return {
20462
21495
  fileViewWidth: DESKTOP_DEFAULT_FILE_VIEW_WIDTH,
@@ -20491,6 +21524,18 @@ function storeDesktopSidePanelWidths(widths) {
20491
21524
  function sameDesktopSidePanelWidths(left, right) {
20492
21525
  return left.fileViewWidth === right.fileViewWidth && left.gitPanelWidth === right.gitPanelWidth;
20493
21526
  }
21527
+ function clampStoredDesktopTerminalPanelHeight(value) {
21528
+ if (value == null || value === "") return DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT;
21529
+ const height = typeof value === "number" ? value : Number(value);
21530
+ if (!Number.isFinite(height)) return DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT;
21531
+ return Math.round(Math.min(DESKTOP_MAX_STORED_TERMINAL_PANEL_HEIGHT, Math.max(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, height)));
21532
+ }
21533
+ function readStoredDesktopTerminalPanelHeight() {
21534
+ return clampStoredDesktopTerminalPanelHeight(safeLocalStorageGetItem(TERMINAL_PANEL_HEIGHT_STORAGE_KEY));
21535
+ }
21536
+ function storeDesktopTerminalPanelHeight(height) {
21537
+ safeLocalStorageSetItem(TERMINAL_PANEL_HEIGHT_STORAGE_KEY, String(clampStoredDesktopTerminalPanelHeight(height)));
21538
+ }
20494
21539
  function clampDesktopPanelSize(value, min, max) {
20495
21540
  if (!Number.isFinite(value)) return min;
20496
21541
  if (max <= min) return Math.max(0, max);
@@ -20535,7 +21580,86 @@ function fitDesktopSidePanelWidths({ availableWidth, fileOpen, fileWidth, gitOpe
20535
21580
  sideWidth: nextFileWidth + nextGitWidth
20536
21581
  };
20537
21582
  }
20538
- function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, onResizeStart, size }) {
21583
+ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, onResizeStart, size }) {
21584
+ const propsRef = useLatestRef({
21585
+ max,
21586
+ min,
21587
+ onResize,
21588
+ onResizeEnd,
21589
+ onResizeStart,
21590
+ size
21591
+ });
21592
+ const [horizontalResizeCursor, setHorizontalResizeCursor] = import_react.useState("col-resize");
21593
+ const [resizeTargetMinimumSize, setResizeTargetMinimumSize] = import_react.useState(10);
21594
+ import_react.useEffect(() => {
21595
+ const updateResizeTarget = () => {
21596
+ setResizeTargetMinimumSize(getSidebarResizeTargetMinimumSize());
21597
+ };
21598
+ const coarsePointerQuery = window.matchMedia("(pointer:coarse)");
21599
+ setHorizontalResizeCursor(getSidebarHorizontalResizeCursor());
21600
+ updateResizeTarget();
21601
+ coarsePointerQuery.addEventListener("change", updateResizeTarget);
21602
+ return () => {
21603
+ coarsePointerQuery.removeEventListener("change", updateResizeTarget);
21604
+ };
21605
+ }, []);
21606
+ const horizontalResizeCursorClass = horizontalResizeCursor === "ew-resize" ? "cursor-ew-resize" : "cursor-col-resize";
21607
+ const resizeTo = (nextSize) => {
21608
+ const current = propsRef.current;
21609
+ current.onResize(clampDesktopPanelSize(nextSize, current.min, current.max));
21610
+ };
21611
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21612
+ role: "separator",
21613
+ "aria-label": label,
21614
+ "aria-orientation": "vertical",
21615
+ tabIndex: 0,
21616
+ style: {
21617
+ "--resize-target-width": `${resizeTargetMinimumSize}px`,
21618
+ cursor: horizontalResizeCursor
21619
+ },
21620
+ className: `absolute inset-y-0 left-0 z-20 w-(--resize-target-width) -translate-x-1/2 touch-none bg-transparent outline-hidden after:absolute after:inset-y-0 after:left-1/2 after:w-px after:bg-transparent focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 ${resizeRailPrimaryInteractiveClass} ${horizontalResizeCursorClass}`,
21621
+ onKeyDown: (event) => {
21622
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
21623
+ event.preventDefault();
21624
+ const delta = event.shiftKey ? 48 : 16;
21625
+ resizeTo(propsRef.current.size + (event.key === "ArrowLeft" ? delta : -delta));
21626
+ },
21627
+ onPointerDown: (event) => {
21628
+ if (event.button !== 0) return;
21629
+ event.preventDefault();
21630
+ const current = propsRef.current;
21631
+ const startX = event.clientX;
21632
+ const startSize = current.size;
21633
+ const cursor = getSidebarHorizontalResizeCursor();
21634
+ const previousCursor = document.body.style.cursor;
21635
+ const previousUserSelect = document.body.style.userSelect;
21636
+ const cleanupGlobalResizeCursor = installGlobalResizeCursor(cursor);
21637
+ current.onResizeStart?.();
21638
+ Object.assign(document.body.style, {
21639
+ userSelect: "none",
21640
+ cursor
21641
+ });
21642
+ const handlePointerMove = (moveEvent) => {
21643
+ resizeTo(startSize + startX - moveEvent.clientX);
21644
+ };
21645
+ const handlePointerUp = () => {
21646
+ cleanupGlobalResizeCursor();
21647
+ Object.assign(document.body.style, {
21648
+ userSelect: previousUserSelect,
21649
+ cursor: previousCursor
21650
+ });
21651
+ document.removeEventListener("pointermove", handlePointerMove);
21652
+ document.removeEventListener("pointerup", handlePointerUp);
21653
+ document.removeEventListener("pointercancel", handlePointerUp);
21654
+ propsRef.current.onResizeEnd?.();
21655
+ };
21656
+ document.addEventListener("pointermove", handlePointerMove);
21657
+ document.addEventListener("pointerup", handlePointerUp);
21658
+ document.addEventListener("pointercancel", handlePointerUp);
21659
+ }
21660
+ });
21661
+ }
21662
+ function AppShellDesktopVerticalResizeHandle({ label, max, min, onResize, onResizeEnd, onResizeStart, size }) {
20539
21663
  const propsRef = useLatestRef({
20540
21664
  max,
20541
21665
  min,
@@ -20544,21 +21668,21 @@ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, o
20544
21668
  onResizeStart,
20545
21669
  size
20546
21670
  });
20547
- const [horizontalResizeCursor, setHorizontalResizeCursor] = import_react.useState("col-resize");
21671
+ const [verticalResizeCursor, setVerticalResizeCursor] = import_react.useState("row-resize");
20548
21672
  const [resizeTargetMinimumSize, setResizeTargetMinimumSize] = import_react.useState(10);
20549
21673
  import_react.useEffect(() => {
20550
21674
  const updateResizeTarget = () => {
20551
21675
  setResizeTargetMinimumSize(getSidebarResizeTargetMinimumSize());
20552
21676
  };
20553
21677
  const coarsePointerQuery = window.matchMedia("(pointer:coarse)");
20554
- setHorizontalResizeCursor(getSidebarHorizontalResizeCursor());
21678
+ setVerticalResizeCursor(getSidebarVerticalResizeCursor());
20555
21679
  updateResizeTarget();
20556
21680
  coarsePointerQuery.addEventListener("change", updateResizeTarget);
20557
21681
  return () => {
20558
21682
  coarsePointerQuery.removeEventListener("change", updateResizeTarget);
20559
21683
  };
20560
21684
  }, []);
20561
- const horizontalResizeCursorClass = horizontalResizeCursor === "ew-resize" ? "cursor-ew-resize" : "cursor-col-resize";
21685
+ const verticalResizeCursorClass = verticalResizeCursor === "ns-resize" ? "cursor-ns-resize" : "cursor-row-resize";
20562
21686
  const resizeTo = (nextSize) => {
20563
21687
  const current = propsRef.current;
20564
21688
  current.onResize(clampDesktopPanelSize(nextSize, current.min, current.max));
@@ -20566,26 +21690,26 @@ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, o
20566
21690
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20567
21691
  role: "separator",
20568
21692
  "aria-label": label,
20569
- "aria-orientation": "vertical",
21693
+ "aria-orientation": "horizontal",
20570
21694
  tabIndex: 0,
20571
21695
  style: {
20572
- "--resize-target-width": `${resizeTargetMinimumSize}px`,
20573
- cursor: horizontalResizeCursor
21696
+ "--resize-target-height": `${resizeTargetMinimumSize}px`,
21697
+ cursor: verticalResizeCursor
20574
21698
  },
20575
- className: `absolute inset-y-0 left-0 z-20 w-(--resize-target-width) -translate-x-1/2 touch-none bg-transparent outline-hidden after:absolute after:inset-y-0 after:left-1/2 after:w-px after:bg-transparent focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 ${resizeRailPrimaryInteractiveClass} ${horizontalResizeCursorClass}`,
21699
+ 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}`,
20576
21700
  onKeyDown: (event) => {
20577
- if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
21701
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
20578
21702
  event.preventDefault();
20579
21703
  const delta = event.shiftKey ? 48 : 16;
20580
- resizeTo(propsRef.current.size + (event.key === "ArrowLeft" ? delta : -delta));
21704
+ resizeTo(propsRef.current.size + (event.key === "ArrowUp" ? delta : -delta));
20581
21705
  },
20582
21706
  onPointerDown: (event) => {
20583
21707
  if (event.button !== 0) return;
20584
21708
  event.preventDefault();
20585
21709
  const current = propsRef.current;
20586
- const startX = event.clientX;
21710
+ const startY = event.clientY;
20587
21711
  const startSize = current.size;
20588
- const cursor = getSidebarHorizontalResizeCursor();
21712
+ const cursor = getSidebarVerticalResizeCursor();
20589
21713
  const previousCursor = document.body.style.cursor;
20590
21714
  const previousUserSelect = document.body.style.userSelect;
20591
21715
  const cleanupGlobalResizeCursor = installGlobalResizeCursor(cursor);
@@ -20595,7 +21719,7 @@ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, o
20595
21719
  cursor
20596
21720
  });
20597
21721
  const handlePointerMove = (moveEvent) => {
20598
- resizeTo(startSize + startX - moveEvent.clientX);
21722
+ resizeTo(startSize + startY - moveEvent.clientY);
20599
21723
  };
20600
21724
  const handlePointerUp = () => {
20601
21725
  cleanupGlobalResizeCursor();
@@ -20617,18 +21741,24 @@ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, o
20617
21741
  function AppShellTabsController(props) {
20618
21742
  return useAppShellTabsControllerView(props);
20619
21743
  }
20620
- 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 }) {
21744
+ 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 }) {
20621
21745
  const currentTab = useSelector(appUiStore, (state) => state.currentTab);
20622
21746
  const isDraftSession = useSelector(sessionStore, (sessionState) => sessionState.draft);
21747
+ const terminalSession = useSelector(sessionStore, (sessionState) => ({
21748
+ cwd: sessionState.cwd,
21749
+ sessionId: sessionState.sessionId
21750
+ }), { compare: shallowTerminalSession });
20623
21751
  const showTabsList = isMobile || !isDraftSession || isSessionViewLoading;
20624
- const sessionVisibleClassName = currentTab === "git" ? "hidden min-h-0 flex-1 flex-col md:flex" : "flex min-h-0 flex-1 flex-col";
21752
+ const sessionVisibleClassName = currentTab === "session" ? "flex min-h-0 flex-1 flex-col" : "hidden min-h-0 flex-1 flex-col md:flex";
20625
21753
  const mobileGitClassName = currentTab === "git" ? "min-h-0 flex-1 overflow-hidden md:hidden" : "hidden";
21754
+ const mobileTerminalClassName = currentTab === "terminal" ? "min-h-0 flex-1 overflow-hidden md:hidden" : "hidden";
20626
21755
  const rightSidebarHasVisibleFiles = useSelector(rightSidebarStore, selectRightSidebarHasVisibleFiles);
20627
21756
  const desktopGitPanelOpen = !isMobile && gitPanelOpen;
20628
21757
  const desktopFileViewOpen = !isMobile && gitPanelOpen && rightSidebarHasVisibleFiles;
20629
21758
  const desktopSideWorkspaceOpen = desktopFileViewOpen || desktopGitPanelOpen;
20630
21759
  const desktopLayoutRef = import_react.useRef(null);
20631
21760
  const [desktopLayoutWidth, setDesktopLayoutWidth] = import_react.useState(0);
21761
+ const [desktopLayoutHeight, setDesktopLayoutHeight] = import_react.useState(0);
20632
21762
  const [desktopSidePanelWidths, setDesktopSidePanelWidthsState] = import_react.useState(() => defaultDesktopSidePanelWidths());
20633
21763
  const desktopSidePanelWidthsLoadedRef = import_react.useRef(false);
20634
21764
  const desktopSidePanelWidthsRef = import_react.useRef(desktopSidePanelWidths);
@@ -20656,12 +21786,30 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20656
21786
  }));
20657
21787
  };
20658
21788
  const [desktopGitPanelMounted, setDesktopGitPanelMounted] = import_react.useState(desktopGitPanelOpen);
21789
+ const [desktopTerminalPanelHeight, setDesktopTerminalPanelHeightState] = import_react.useState(() => DESKTOP_DEFAULT_TERMINAL_PANEL_HEIGHT);
21790
+ const desktopTerminalPanelHeightLoadedRef = import_react.useRef(false);
21791
+ const desktopTerminalPanelHeightRef = import_react.useRef(desktopTerminalPanelHeight);
21792
+ desktopTerminalPanelHeightRef.current = desktopTerminalPanelHeight;
21793
+ const [desktopTerminalPanelMounted, setDesktopTerminalPanelMounted] = import_react.useState(!isMobile && terminalPanelOpen);
20659
21794
  const [desktopPanelResizing, setDesktopPanelResizing] = import_react.useState(false);
21795
+ const [desktopTerminalResizing, setDesktopTerminalResizing] = import_react.useState(false);
21796
+ const setDesktopTerminalPanelHeight = (action) => {
21797
+ const current = desktopTerminalPanelHeightRef.current;
21798
+ const next = clampStoredDesktopTerminalPanelHeight(applyStoreAction(current, action));
21799
+ if (current === next) return;
21800
+ desktopTerminalPanelHeightRef.current = next;
21801
+ setDesktopTerminalPanelHeightState(next);
21802
+ if (desktopTerminalPanelHeightLoadedRef.current) storeDesktopTerminalPanelHeight(next);
21803
+ };
20660
21804
  import_react.useEffect(() => {
20661
21805
  const storedWidths = readStoredDesktopSidePanelWidths();
21806
+ const storedTerminalPanelHeight = readStoredDesktopTerminalPanelHeight();
20662
21807
  desktopSidePanelWidthsLoadedRef.current = true;
21808
+ desktopTerminalPanelHeightLoadedRef.current = true;
20663
21809
  desktopSidePanelWidthsRef.current = storedWidths;
21810
+ desktopTerminalPanelHeightRef.current = storedTerminalPanelHeight;
20664
21811
  setDesktopSidePanelWidthsState((current) => sameDesktopSidePanelWidths(current, storedWidths) ? current : storedWidths);
21812
+ setDesktopTerminalPanelHeightState((current) => current === storedTerminalPanelHeight ? current : storedTerminalPanelHeight);
20665
21813
  }, []);
20666
21814
  const sessionPane = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20667
21815
  className: "flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
@@ -20689,7 +21837,7 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20689
21837
  viewerContextId,
20690
21838
  workingStateStore
20691
21839
  })
20692
- }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21840
+ }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20693
21841
  className: mobileGitClassName,
20694
21842
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellGitPanelController, {
20695
21843
  viewerContextId,
@@ -20705,32 +21853,50 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20705
21853
  onOpenFile: onOpenFileViewTab,
20706
21854
  onReorderFiles: onReorderFileViewTabs
20707
21855
  })
20708
- }) : null]
21856
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21857
+ className: mobileTerminalClassName,
21858
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalPanel, {
21859
+ active: currentTab === "terminal",
21860
+ cwd: terminalSession.cwd,
21861
+ onClose: onCloseTerminalPanel,
21862
+ sessionId: terminalSession.sessionId,
21863
+ showCloseButton: false,
21864
+ viewerContextId
21865
+ })
21866
+ })] }) : null]
20709
21867
  });
20710
21868
  import_react.useLayoutEffect(() => {
20711
21869
  if (isMobile) {
20712
21870
  setDesktopGitPanelMounted(false);
21871
+ setDesktopTerminalPanelMounted(false);
20713
21872
  return;
20714
21873
  }
20715
21874
  if (desktopSideWorkspaceOpen) setDesktopGitPanelMounted(true);
20716
- }, [desktopSideWorkspaceOpen, isMobile]);
21875
+ if (terminalPanelOpen) setDesktopTerminalPanelMounted(true);
21876
+ }, [
21877
+ desktopSideWorkspaceOpen,
21878
+ isMobile,
21879
+ terminalPanelOpen
21880
+ ]);
20717
21881
  import_react.useLayoutEffect(() => {
20718
21882
  if (isMobile) return;
20719
21883
  const element = desktopLayoutRef.current;
20720
21884
  if (!element) return;
20721
- const updateWidth = () => {
21885
+ const updateSize = () => {
20722
21886
  setDesktopLayoutWidth(element.clientWidth);
21887
+ setDesktopLayoutHeight(element.clientHeight);
20723
21888
  };
20724
- updateWidth();
21889
+ updateSize();
20725
21890
  if (typeof ResizeObserver === "undefined") {
20726
- window.addEventListener("resize", updateWidth);
20727
- return () => window.removeEventListener("resize", updateWidth);
21891
+ window.addEventListener("resize", updateSize);
21892
+ return () => window.removeEventListener("resize", updateSize);
20728
21893
  }
20729
- const observer = new ResizeObserver(updateWidth);
21894
+ const observer = new ResizeObserver(updateSize);
20730
21895
  observer.observe(element);
20731
21896
  return () => observer.disconnect();
20732
21897
  }, [isMobile]);
20733
21898
  const desktopAvailableWidth = desktopLayoutWidth || (typeof window === "undefined" ? 1200 : window.innerWidth);
21899
+ const desktopAvailableHeight = desktopLayoutHeight || (typeof window === "undefined" ? 800 : window.innerHeight);
20734
21900
  const desktopGitPanelRendered = desktopSideWorkspaceOpen || desktopGitPanelMounted || desktopGitPanelOpen;
20735
21901
  const desktopFittedWidths = fitDesktopSidePanelWidths({
20736
21902
  availableWidth: desktopAvailableWidth,
@@ -20741,14 +21907,25 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20741
21907
  });
20742
21908
  const desktopSideWorkspaceRendered = desktopSideWorkspaceOpen || desktopGitPanelMounted;
20743
21909
  const desktopSideWorkspaceWidth = desktopSideWorkspaceOpen ? desktopFittedWidths.sideWidth : 0;
20744
- const desktopTransitionClassName = !desktopPanelResizing ? "transition-[width] duration-200 ease-linear" : "";
20745
- const desktopPanelGroupClassName = "relative flex h-full min-h-0 w-full flex-1 overflow-hidden";
21910
+ const desktopTerminalOpen = !isMobile && terminalPanelOpen;
21911
+ const desktopTerminalRendered = desktopTerminalOpen || desktopTerminalPanelMounted;
21912
+ const desktopTerminalMaxHeight = Math.max(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopAvailableHeight - DESKTOP_MIN_TOP_PANEL_HEIGHT);
21913
+ const desktopFittedTerminalPanelHeight = clampDesktopPanelSize(desktopTerminalPanelHeight, Math.min(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopTerminalMaxHeight), desktopTerminalMaxHeight);
21914
+ const desktopTerminalVisibleHeight = desktopTerminalOpen ? desktopFittedTerminalPanelHeight : 0;
21915
+ const desktopTransitionClassName = !desktopPanelResizing && !desktopTerminalResizing ? "transition-[width,height] duration-200 ease-linear" : "";
21916
+ const desktopPanelGroupClassName = "relative flex min-h-0 w-full shrink-0 overflow-hidden";
20746
21917
  const desktopResizeStart = () => {
20747
21918
  setDesktopPanelResizing(true);
20748
21919
  };
20749
21920
  const desktopResizeEnd = () => {
20750
21921
  setDesktopPanelResizing(false);
20751
21922
  };
21923
+ const desktopTerminalResizeStart = () => {
21924
+ setDesktopTerminalResizing(true);
21925
+ };
21926
+ const desktopTerminalResizeEnd = () => {
21927
+ setDesktopTerminalResizing(false);
21928
+ };
20752
21929
  const desktopSideWorkspaceMaxWidth = Math.max(0, desktopAvailableWidth - DESKTOP_MIN_SESSION_WIDTH);
20753
21930
  const setDesktopSideWorkspaceWidth = (nextSize) => {
20754
21931
  if (!desktopFileViewOpen) {
@@ -20757,6 +21934,9 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20757
21934
  }
20758
21935
  setDesktopFileViewWidth(Math.max(DESKTOP_MIN_SIDE_PANEL_WIDTH, nextSize - desktopGitPanelWidth));
20759
21936
  };
21937
+ const setDesktopTerminalHeight = (nextSize) => {
21938
+ setDesktopTerminalPanelHeight(clampDesktopPanelSize(nextSize, Math.min(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopTerminalMaxHeight), desktopTerminalMaxHeight));
21939
+ };
20760
21940
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tabs$1, {
20761
21941
  value: currentTab,
20762
21942
  onValueChange,
@@ -20769,54 +21949,83 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
20769
21949
  children: sessionPane
20770
21950
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20771
21951
  ref: desktopLayoutRef,
20772
- className: desktopPanelGroupClassName,
20773
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20774
- "data-desktop-panel": "session",
20775
- className: `h-full min-h-0 min-w-0 shrink-0 overflow-hidden ${desktopTransitionClassName}`,
20776
- style: { width: desktopSideWorkspaceOpen ? `calc(100% - ${desktopSideWorkspaceWidth}px)` : "100%" },
20777
- children: sessionPane
20778
- }), desktopSideWorkspaceRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", {
20779
- "aria-label": "Desktop side workspace",
20780
- "aria-hidden": !desktopSideWorkspaceOpen,
20781
- "data-state": desktopSideWorkspaceOpen ? "open" : "closed",
20782
- "data-desktop-panel": "side-workspace",
20783
- className: `flex h-full min-h-0 min-w-0 shrink-0 overflow-visible bg-background data-[state=closed]:pointer-events-none ${desktopTransitionClassName}`,
20784
- style: { width: `${desktopSideWorkspaceWidth}px` },
20785
- children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20786
- "data-desktop-panel": "git",
20787
- className: `relative h-full min-h-0 min-w-0 shrink-0 overflow-visible ${desktopTransitionClassName}`,
21952
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
21953
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21954
+ className: desktopPanelGroupClassName,
21955
+ style: { height: desktopTerminalOpen ? `calc(100% - ${desktopTerminalVisibleHeight}px)` : "100%" },
21956
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21957
+ "data-desktop-panel": "session",
21958
+ className: `h-full min-h-0 min-w-0 shrink-0 overflow-hidden max-md:w-full! ${desktopTransitionClassName}`,
21959
+ style: { width: desktopSideWorkspaceOpen ? `calc(100% - ${desktopSideWorkspaceWidth}px)` : "100%" },
21960
+ children: sessionPane
21961
+ }), desktopSideWorkspaceRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", {
21962
+ "aria-label": "Desktop side workspace",
21963
+ "aria-hidden": !desktopSideWorkspaceOpen,
21964
+ "data-state": desktopSideWorkspaceOpen ? "open" : "closed",
21965
+ "data-desktop-panel": "side-workspace",
21966
+ 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}`,
20788
21967
  style: { width: `${desktopSideWorkspaceWidth}px` },
20789
- children: [desktopGitPanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopResizeHandle, {
20790
- label: "Resize right sidebar",
20791
- min: Math.min(DESKTOP_MIN_SIDE_PANEL_WIDTH, desktopSideWorkspaceMaxWidth),
20792
- max: desktopSideWorkspaceMaxWidth,
20793
- size: desktopSideWorkspaceWidth,
20794
- onResize: setDesktopSideWorkspaceWidth,
20795
- onResizeStart: desktopResizeStart,
20796
- onResizeEnd: desktopResizeEnd
20797
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20798
- className: "h-full min-h-0 min-w-0 overflow-hidden",
20799
- children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopGitPanel, {
20800
- viewerContextId,
20801
- sessionStore,
20802
- active: desktopGitPanelOpen,
20803
- composerDiffLineComments,
20804
- rightSidebarStore,
20805
- onAddDiffLineComment,
20806
- onCloseAllFiles: onCloseAllFileViewTabs,
20807
- onCloseFile: onCloseFileViewTab,
20808
- onCloseFilesToRight: onCloseFileViewTabsToRight,
20809
- onCloseOtherFiles: onCloseOtherFileViewTabs,
20810
- onOpenFile: onOpenFileViewTab,
20811
- onReorderFiles: onReorderFileViewTabs
20812
- }) : null
20813
- })]
20814
- }) : null
21968
+ children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21969
+ "data-desktop-panel": "git",
21970
+ className: `relative h-full min-h-0 min-w-0 shrink-0 overflow-visible ${desktopTransitionClassName}`,
21971
+ style: { width: `${desktopSideWorkspaceWidth}px` },
21972
+ children: [desktopGitPanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopResizeHandle, {
21973
+ label: "Resize right sidebar",
21974
+ min: Math.min(DESKTOP_MIN_SIDE_PANEL_WIDTH, desktopSideWorkspaceMaxWidth),
21975
+ max: desktopSideWorkspaceMaxWidth,
21976
+ size: desktopSideWorkspaceWidth,
21977
+ onResize: setDesktopSideWorkspaceWidth,
21978
+ onResizeStart: desktopResizeStart,
21979
+ onResizeEnd: desktopResizeEnd
21980
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21981
+ className: "h-full min-h-0 min-w-0 overflow-hidden",
21982
+ children: desktopGitPanelRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopGitPanel, {
21983
+ viewerContextId,
21984
+ sessionStore,
21985
+ active: desktopGitPanelOpen,
21986
+ composerDiffLineComments,
21987
+ rightSidebarStore,
21988
+ onAddDiffLineComment,
21989
+ onCloseAllFiles: onCloseAllFileViewTabs,
21990
+ onCloseFile: onCloseFileViewTab,
21991
+ onCloseFilesToRight: onCloseFileViewTabsToRight,
21992
+ onCloseOtherFiles: onCloseOtherFileViewTabs,
21993
+ onOpenFile: onOpenFileViewTab,
21994
+ onReorderFiles: onReorderFileViewTabs
21995
+ }) : null
21996
+ })]
21997
+ }) : null
21998
+ }) : null]
21999
+ }), desktopTerminalRendered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
22000
+ "aria-label": "Terminal",
22001
+ "aria-hidden": !desktopTerminalOpen,
22002
+ "data-state": desktopTerminalOpen ? "open" : "closed",
22003
+ "data-desktop-panel": "terminal",
22004
+ 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}`,
22005
+ style: { height: `${desktopTerminalVisibleHeight}px` },
22006
+ children: [desktopTerminalOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDesktopVerticalResizeHandle, {
22007
+ label: "Resize terminal",
22008
+ min: Math.min(DESKTOP_MIN_TERMINAL_PANEL_HEIGHT, desktopTerminalMaxHeight),
22009
+ max: desktopTerminalMaxHeight,
22010
+ size: desktopTerminalVisibleHeight,
22011
+ onResize: setDesktopTerminalHeight,
22012
+ onResizeStart: desktopTerminalResizeStart,
22013
+ onResizeEnd: desktopTerminalResizeEnd
22014
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
22015
+ className: "h-full min-h-0 w-full min-w-0 overflow-hidden",
22016
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TerminalPanel, {
22017
+ active: desktopTerminalOpen,
22018
+ cwd: terminalSession.cwd,
22019
+ onClose: onCloseTerminalPanel,
22020
+ sessionId: terminalSession.sessionId,
22021
+ viewerContextId
22022
+ })
22023
+ })]
20815
22024
  }) : null]
20816
22025
  })]
20817
22026
  });
20818
22027
  }
20819
- var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ actionsRef, defaultNewSessionDirectory, displaySessionCwd, gitPanelOpen, loadingDisplaySessionTitle, currentSessionPinned, displaySettingsStore, isSessionViewLoading, newSessionDirectoryOptions, onToggleGitPanel, sessionStore, viewerContextId }) {
22028
+ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ actionsRef, defaultNewSessionDirectory, displaySessionCwd, gitPanelOpen, terminalPanelOpen, loadingDisplaySessionTitle, currentSessionPinned, displaySettingsStore, isSessionViewLoading, newSessionDirectoryOptions, onToggleGitPanel, onToggleTerminalPanel, sessionStore, viewerContextId }) {
20820
22029
  const sessionHeaderState = useSelector(sessionStore, (sessionState) => ({
20821
22030
  firstMessage: sessionState.firstMessage,
20822
22031
  hideThinkingBlock: sessionState.hideThinkingBlock,
@@ -20825,8 +22034,6 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20825
22034
  sessionStreaming: sessionState.streaming
20826
22035
  }), { compare: shallowRecordEqual });
20827
22036
  const hideToolBlocks = useSelector(displaySettingsStore, (settings) => settings.hideToolBlocks);
20828
- const { isMobile: sidebarIsMobile, openMobile: sidebarOpenMobile, state: sidebarState } = useSidebar();
20829
- const showCollapsedNewSessionButton = !(sidebarIsMobile ? sidebarOpenMobile : sidebarState === "expanded");
20830
22037
  const displaySessionTitle = isSessionViewLoading ? loadingDisplaySessionTitle : getCurrentSessionTitleFromState(sessionHeaderState);
20831
22038
  const moveSessionDirectoryOptions = newSessionDirectoryOptions.filter((option) => option.path !== displaySessionCwd);
20832
22039
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -20835,25 +22042,26 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20835
22042
  className: "relative flex w-full items-center gap-1",
20836
22043
  children: [
20837
22044
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "shrink-0" }),
20838
- showCollapsedNewSessionButton ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
22045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
20839
22046
  title: defaultNewSessionDirectory ? `Create a new session in ${formatDisplayPath$1(defaultNewSessionDirectory)}` : "Create a new session",
20840
22047
  kbd: formatShortcutLabel("Control+N"),
20841
22048
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
20842
22049
  size: "icon",
20843
22050
  variant: "ghost",
20844
- className: "shrink-0",
22051
+ className: "shrink-0 md:group-data-[sidebar-state=expanded]/sidebar-wrapper:hidden",
20845
22052
  "aria-label": "Create a new session",
20846
22053
  onClick: () => {
20847
22054
  actionsRef.current.createSession();
20848
22055
  },
20849
22056
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SquarePen, {})
20850
22057
  })
20851
- }) : null,
22058
+ }),
20852
22059
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20853
22060
  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",
20854
22061
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20855
22062
  className: "flex max-w-full min-w-0 items-center justify-center gap-1.5",
20856
22063
  children: [!isSessionViewLoading && sessionHeaderState.sessionStreaming ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
22064
+ size: "xs",
20857
22065
  className: "shrink-0",
20858
22066
  "aria-label": "Session streaming"
20859
22067
  }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
@@ -20864,7 +22072,7 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
20864
22072
  })
20865
22073
  })]
20866
22074
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20867
- className: "flex max-w-full min-w-0 items-center justify-center gap-x-3",
22075
+ className: "flex max-w-full min-w-0 items-center justify-center gap-x-2",
20868
22076
  children: [displaySessionCwd ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20869
22077
  className: "inline-flex min-w-0 items-center text-xs text-muted-foreground",
20870
22078
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -21002,6 +22210,19 @@ var AppShellSessionHeader = import_react.memo(function AppShellSessionHeader({ a
21002
22210
  })
21003
22211
  ]
21004
22212
  })] }),
22213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
22214
+ title: "Toggle terminal",
22215
+ kbd: formatShortcutLabel("Control+`"),
22216
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
22217
+ size: "icon",
22218
+ variant: "ghost",
22219
+ className: "hidden md:inline-flex",
22220
+ "aria-pressed": terminalPanelOpen,
22221
+ "aria-label": "Toggle terminal panel",
22222
+ onClick: onToggleTerminalPanel,
22223
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SquareTerminal, {})
22224
+ })
22225
+ }),
21005
22226
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
21006
22227
  title: "Toggle right sidebar",
21007
22228
  kbd: formatShortcutLabel("Control+\\"),
@@ -21046,45 +22267,10 @@ function useDelayedTrue(value, delayMs) {
21046
22267
  }, [delayMs, value]);
21047
22268
  return delayedValue;
21048
22269
  }
21049
- function tildePath(value) {
21050
- return value.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~");
21051
- }
21052
- function splitDisplayPath(value) {
21053
- if (!value) return {
21054
- leading: "",
21055
- trailing: ""
21056
- };
21057
- const trimmed = value.replace(/[\\/]+$/, "");
21058
- if (!trimmed) return {
21059
- leading: "",
21060
- trailing: value
21061
- };
21062
- const suffix = value.slice(trimmed.length);
21063
- const separatorIndex = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
21064
- if (separatorIndex < 0 || separatorIndex === trimmed.length - 1) return {
21065
- leading: "",
21066
- trailing: `${trimmed}${suffix}`
21067
- };
21068
- return {
21069
- leading: trimmed.slice(0, separatorIndex + 1),
21070
- trailing: `${trimmed.slice(separatorIndex + 1)}${suffix}`
21071
- };
21072
- }
21073
22270
  function DirectoryPathLabel({ path }) {
21074
- const displayPath = tildePath(path);
21075
- const { leading, trailing } = splitDisplayPath(displayPath);
21076
22271
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21077
- className: "block min-w-0 text-sm text-sidebar-foreground",
21078
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
21079
- className: "flex min-w-0 items-center",
21080
- children: [leading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21081
- className: "truncate text-sidebar-foreground/60",
21082
- children: leading
21083
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21084
- className: "shrink-0 font-medium",
21085
- children: trailing || displayPath
21086
- })]
21087
- })
22272
+ className: "block min-w-0 truncate text-sm font-medium text-sidebar-foreground",
22273
+ children: formatFolderName$1(path) || path
21088
22274
  });
21089
22275
  }
21090
22276
  var EMPTY_DIRECTORY_SESSION_KEYS = [];
@@ -21324,7 +22510,7 @@ function SidebarSessionItem({ entryKey, directorySessionsStore, activeSessionSto
21324
22510
  const timestamp = entry.lastMessageAt || entry.modified || entry.lastUserMessageAt;
21325
22511
  const hasTimestamp = isValidSidebarTimestamp(timestamp);
21326
22512
  const lastMessagePreview = entry.lastMessagePreview?.trim() || "";
21327
- const pinnedDirectoryLabel = isPinned ? formatDisplayPath$1(entry.cwd?.trim() || entry.path?.trim() || "") : "";
22513
+ const pinnedDirectoryLabel = isPinned ? formatFolderName$1(entry.cwd?.trim() || entry.path?.trim() || "") : "";
21328
22514
  const exactTimestamp = hasTimestamp && timestamp ? new Date(timestamp).toLocaleString() : void 0;
21329
22515
  const showUnread = Boolean(entry.unread) && !entry.streaming;
21330
22516
  const isOptimisticSession = Boolean(entry.optimistic || entry.id?.startsWith("optimistic:") && !entry.path);
@@ -21347,7 +22533,10 @@ function SidebarSessionItem({ entryKey, directorySessionsStore, activeSessionSto
21347
22533
  },
21348
22534
  children: [entry.streaming ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21349
22535
  className: "absolute top-2.5 left-2 flex size-4 items-center justify-center",
21350
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { "aria-label": "Session streaming" })
22536
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
22537
+ size: "xs",
22538
+ "aria-label": "Session streaming"
22539
+ })
21351
22540
  }) : showUnread ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21352
22541
  className: "absolute top-2.5 left-2 flex size-4 items-center justify-center",
21353
22542
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -21524,7 +22713,10 @@ var DirectorySessionGroup = import_react.memo(function DirectorySessionGroup({ d
21524
22713
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirectoryPathLabel, { path: directory })
21525
22714
  }),
21526
22715
  !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,
21527
- showHeaderLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { className: "shrink-0" }) : null
22716
+ showHeaderLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {
22717
+ size: "xs",
22718
+ className: "shrink-0"
22719
+ }) : null
21528
22720
  ]
21529
22721
  }),
21530
22722
  onCreateSessionInDirectory && !overlay && !isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
@@ -21594,7 +22786,7 @@ var DirectorySessionGroup = import_react.memo(function DirectorySessionGroup({ d
21594
22786
  ]
21595
22787
  })] }) : 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", {
21596
22788
  className: "flex items-center gap-2 p-2 text-sm text-sidebar-foreground/70",
21597
- children: [showLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
22789
+ children: [showLoadingSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "xs" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
21598
22790
  className: "size-4",
21599
22791
  "aria-hidden": "true"
21600
22792
  }), "Loading sessions…"]
@@ -22912,12 +24104,13 @@ function sameComposerPendingMessages(left, right) {
22912
24104
  return true;
22913
24105
  }
22914
24106
  function sameAppShellComposerSnapshot(left, right) {
22915
- return left.activeSessionId === right.activeSessionId && left.awaitingFirstTurn === right.awaitingFirstTurn && left.centerMessages === right.centerMessages && sameComposerDiffLineComments(left.composerDiffLineComments, right.composerDiffLineComments) && left.composerSkill === right.composerSkill && left.composerSyncNonce === right.composerSyncNonce && left.composerText === right.composerText && left.disabled === right.disabled && left.isStreaming === right.isStreaming && left.isSubmitting === right.isSubmitting && left.viewerContextId === right.viewerContextId && sameComposerPromptImages(left.composerImages, right.composerImages) && sameComposerPendingMessages(left.currentPendingMessages, right.currentPendingMessages);
24107
+ return left.activeSessionId === right.activeSessionId && left.awaitingFirstTurn === right.awaitingFirstTurn && left.canStartPendingQueue === right.canStartPendingQueue && left.centerMessages === right.centerMessages && sameComposerDiffLineComments(left.composerDiffLineComments, right.composerDiffLineComments) && left.composerSkill === right.composerSkill && left.composerSyncNonce === right.composerSyncNonce && left.composerText === right.composerText && left.disabled === right.disabled && left.isCompacting === right.isCompacting && left.isStreaming === right.isStreaming && left.isSubmitting === right.isSubmitting && left.viewerContextId === right.viewerContextId && sameComposerPromptImages(left.composerImages, right.composerImages) && sameComposerPendingMessages(left.currentPendingMessages, right.currentPendingMessages);
22916
24108
  }
22917
24109
  function createInitialAppShellComposerSnapshot(viewerContextId) {
22918
24110
  return {
22919
24111
  activeSessionId: void 0,
22920
24112
  awaitingFirstTurn: false,
24113
+ canStartPendingQueue: false,
22921
24114
  centerMessages: false,
22922
24115
  composerDiffLineComments: EMPTY_COMPOSER_DIFF_LINE_COMMENTS,
22923
24116
  composerImages: EMPTY_COMPOSER_IMAGES,
@@ -22926,6 +24119,7 @@ function createInitialAppShellComposerSnapshot(viewerContextId) {
22926
24119
  composerText: "",
22927
24120
  currentPendingMessages: EMPTY_COMPOSER_PENDING_MESSAGES,
22928
24121
  disabled: false,
24122
+ isCompacting: false,
22929
24123
  isStreaming: false,
22930
24124
  isSubmitting: false,
22931
24125
  viewerContextId
@@ -22988,6 +24182,143 @@ function createPicoLatestThrottler(options) {
22988
24182
  cancel: () => throttler.cancel()
22989
24183
  };
22990
24184
  }
24185
+ var OFFLINE_PROMPT_DB_NAME = "pico-offline-prompts";
24186
+ var OFFLINE_PROMPT_DB_VERSION = 1;
24187
+ var OFFLINE_PROMPT_STORE_NAME = "prompts";
24188
+ var OFFLINE_PROMPT_FALLBACK_STORAGE_KEY = "pico-offline-prompts";
24189
+ var dbPromise;
24190
+ function hasIndexedDb() {
24191
+ return typeof indexedDB !== "undefined";
24192
+ }
24193
+ function openOfflinePromptDb() {
24194
+ if (!hasIndexedDb()) return Promise.resolve(null);
24195
+ dbPromise ??= new Promise((resolve) => {
24196
+ const request = indexedDB.open(OFFLINE_PROMPT_DB_NAME, OFFLINE_PROMPT_DB_VERSION);
24197
+ request.onupgradeneeded = () => {
24198
+ const db = request.result;
24199
+ if (!db.objectStoreNames.contains(OFFLINE_PROMPT_STORE_NAME)) db.createObjectStore(OFFLINE_PROMPT_STORE_NAME, { keyPath: "id" });
24200
+ };
24201
+ request.onerror = () => resolve(null);
24202
+ request.onsuccess = () => resolve(request.result);
24203
+ });
24204
+ return dbPromise;
24205
+ }
24206
+ function fallbackItems() {
24207
+ try {
24208
+ const raw = window.localStorage.getItem(OFFLINE_PROMPT_FALLBACK_STORAGE_KEY);
24209
+ if (!raw) return [];
24210
+ const parsed = JSON.parse(raw);
24211
+ return Array.isArray(parsed) ? normalizeItems(parsed) : [];
24212
+ } catch {
24213
+ return [];
24214
+ }
24215
+ }
24216
+ function rememberFallbackItems(items) {
24217
+ try {
24218
+ window.localStorage.setItem(OFFLINE_PROMPT_FALLBACK_STORAGE_KEY, JSON.stringify(items));
24219
+ } catch {}
24220
+ }
24221
+ function normalizeImage(value) {
24222
+ if (!value || typeof value !== "object") return null;
24223
+ const rawImage = value;
24224
+ if (typeof rawImage.mimeType !== "string") return null;
24225
+ if (typeof rawImage.data !== "string") return null;
24226
+ return {
24227
+ type: "image",
24228
+ mimeType: rawImage.mimeType,
24229
+ data: rawImage.data
24230
+ };
24231
+ }
24232
+ function normalizeItem(value) {
24233
+ if (!value || typeof value !== "object") return null;
24234
+ const rawItem = value;
24235
+ if (typeof rawItem.id !== "string" || !rawItem.id) return null;
24236
+ if (typeof rawItem.contextId !== "string" || !rawItem.contextId) return null;
24237
+ if (typeof rawItem.message !== "string") return null;
24238
+ if (typeof rawItem.clientRequestId !== "string") return null;
24239
+ const images = Array.isArray(rawItem.images) ? rawItem.images.flatMap((image) => {
24240
+ const normalizedImage = normalizeImage(image);
24241
+ return normalizedImage ? [normalizedImage] : [];
24242
+ }) : [];
24243
+ return {
24244
+ id: rawItem.id,
24245
+ contextId: rawItem.contextId,
24246
+ createdAt: typeof rawItem.createdAt === "number" && Number.isFinite(rawItem.createdAt) ? rawItem.createdAt : Date.now(),
24247
+ sessionId: typeof rawItem.sessionId === "string" ? rawItem.sessionId : void 0,
24248
+ targetSessionKey: typeof rawItem.targetSessionKey === "string" ? rawItem.targetSessionKey : void 0,
24249
+ message: rawItem.message,
24250
+ images,
24251
+ streamingBehavior: rawItem.streamingBehavior === "followUp" || rawItem.streamingBehavior === "steer" ? rawItem.streamingBehavior : void 0,
24252
+ pendingId: typeof rawItem.pendingId === "string" ? rawItem.pendingId : void 0,
24253
+ clientRequestId: rawItem.clientRequestId,
24254
+ thinkingLevel: typeof rawItem.thinkingLevel === "string" ? rawItem.thinkingLevel : void 0,
24255
+ draftOwnerKey: typeof rawItem.draftOwnerKey === "string" ? rawItem.draftOwnerKey : void 0,
24256
+ draftCwd: typeof rawItem.draftCwd === "string" ? rawItem.draftCwd : void 0
24257
+ };
24258
+ }
24259
+ function normalizeItems(values) {
24260
+ return values.flatMap((value) => {
24261
+ const normalizedItem = normalizeItem(value);
24262
+ return normalizedItem ? [normalizedItem] : [];
24263
+ }).sort((left, right) => left.createdAt - right.createdAt);
24264
+ }
24265
+ async function enqueueOfflinePrompt(item) {
24266
+ const db = await openOfflinePromptDb();
24267
+ if (!db) {
24268
+ const items = fallbackItems().filter((entry) => entry.id !== item.id);
24269
+ items.push(item);
24270
+ rememberFallbackItems(normalizeItems(items));
24271
+ return;
24272
+ }
24273
+ await new Promise((resolve, reject) => {
24274
+ const transaction = db.transaction(OFFLINE_PROMPT_STORE_NAME, "readwrite");
24275
+ const request = transaction.objectStore(OFFLINE_PROMPT_STORE_NAME).put(item);
24276
+ request.onerror = () => reject(request.error);
24277
+ transaction.oncomplete = () => resolve();
24278
+ transaction.onerror = () => reject(transaction.error);
24279
+ });
24280
+ }
24281
+ async function readOfflinePrompts(contextId) {
24282
+ const db = await openOfflinePromptDb();
24283
+ if (!db) {
24284
+ const items = fallbackItems();
24285
+ return contextId ? items.filter((item) => item.contextId === contextId) : items;
24286
+ }
24287
+ const items = await new Promise((resolve, reject) => {
24288
+ const transaction = db.transaction(OFFLINE_PROMPT_STORE_NAME, "readonly");
24289
+ const request = transaction.objectStore(OFFLINE_PROMPT_STORE_NAME).getAll();
24290
+ request.onerror = () => reject(request.error);
24291
+ request.onsuccess = () => resolve(normalizeItems(request.result));
24292
+ transaction.onerror = () => reject(transaction.error);
24293
+ });
24294
+ return contextId ? items.filter((item) => item.contextId === contextId) : items;
24295
+ }
24296
+ async function removeOfflinePrompt(id) {
24297
+ const db = await openOfflinePromptDb();
24298
+ if (!db) {
24299
+ rememberFallbackItems(fallbackItems().filter((item) => item.id !== id));
24300
+ return;
24301
+ }
24302
+ await new Promise((resolve, reject) => {
24303
+ const transaction = db.transaction(OFFLINE_PROMPT_STORE_NAME, "readwrite");
24304
+ const request = transaction.objectStore(OFFLINE_PROMPT_STORE_NAME).delete(id);
24305
+ request.onerror = () => reject(request.error);
24306
+ transaction.oncomplete = () => resolve();
24307
+ transaction.onerror = () => reject(transaction.error);
24308
+ });
24309
+ }
24310
+ function pendingMessagesFromResponse(response) {
24311
+ if (!("ok" in response) || !response.ok) return null;
24312
+ return response.pendingMessages.map((message) => ({
24313
+ pendingId: message.pendingId,
24314
+ text: message.text,
24315
+ images: message.images.flatMap((image) => {
24316
+ const normalizedImage = normalizePromptImage(image);
24317
+ return normalizedImage ? [normalizedImage] : [];
24318
+ }),
24319
+ streamingBehavior: message.streamingBehavior === "steer" ? "steer" : "followUp"
24320
+ }));
24321
+ }
22991
24322
  function samePromptMutationSessionSnapshot(left, right) {
22992
24323
  return left.assistantOutputSignature === right.assistantOutputSignature && left.cwd === right.cwd && left.draft === right.draft && left.sessionFile === right.sessionFile && left.sessionId === right.sessionId && left.sessionKey === right.sessionKey && left.streaming === right.streaming;
22993
24324
  }
@@ -23027,7 +24358,20 @@ function createLocalPendingId() {
23027
24358
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `optimistic:${crypto.randomUUID()}`;
23028
24359
  return `optimistic:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
23029
24360
  }
23030
- function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallbackSessionId }) {
24361
+ function createClientPromptRequestId() {
24362
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `prompt:${crypto.randomUUID()}`;
24363
+ return `prompt:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
24364
+ }
24365
+ function isOfflinePromptSubmissionError(error) {
24366
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
24367
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
24368
+ return message === "failed to fetch" || message === "load failed" || message.includes("networkerror") || message.includes("network error");
24369
+ }
24370
+ function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallbackSessionId, targetSessionKey }) {
24371
+ if (targetSessionKey) return buildRequestUrl(path, {
24372
+ contextId,
24373
+ searchParams: { sessionKey: targetSessionKey }
24374
+ });
23031
24375
  const sessionKey = sessionState.sessionKey && !sessionState.sessionKey.startsWith("optimistic:") ? sessionState.sessionKey : void 0;
23032
24376
  const shouldUseSessionKey = Boolean(sessionKey && (sessionState.draft || !sessionState.sessionId));
23033
24377
  return buildRequestUrl(path, {
@@ -23036,6 +24380,25 @@ function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallback
23036
24380
  searchParams: shouldUseSessionKey ? { sessionKey } : void 0
23037
24381
  });
23038
24382
  }
24383
+ function buildOfflinePromptRequestUrl(item) {
24384
+ return buildRequestUrl("/api/prompt", {
24385
+ contextId: item.contextId,
24386
+ sessionId: item.targetSessionKey ? void 0 : item.sessionId,
24387
+ searchParams: item.targetSessionKey ? { sessionKey: item.targetSessionKey } : void 0
24388
+ });
24389
+ }
24390
+ function buildOfflinePromptBody(item) {
24391
+ return {
24392
+ message: item.message,
24393
+ images: item.images,
24394
+ streamingBehavior: item.streamingBehavior,
24395
+ pendingId: item.pendingId,
24396
+ clientRequestId: item.clientRequestId,
24397
+ thinkingLevel: item.thinkingLevel,
24398
+ draftOwnerKey: item.draftOwnerKey,
24399
+ draftCwd: item.draftCwd
24400
+ };
24401
+ }
23039
24402
  function isPendingPromptNotFoundError(error) {
23040
24403
  return error instanceof Error && error.message.toLowerCase().includes("pending prompt not found");
23041
24404
  }
@@ -23088,13 +24451,14 @@ function movePendingComposerMessage(messages, pendingId, direction) {
23088
24451
  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 }) {
23089
24452
  const queryClient = useQueryClient();
23090
24453
  const draftSessionLoadingOwnerKeyRef = import_react.useRef(draftSessionLoadingOwnerKey);
24454
+ const pendingNewSessionPromptTargetRef = import_react.useRef(null);
24455
+ const newSessionRequestNonceRef = import_react.useRef(0);
23091
24456
  const pendingDraftPromptRef = import_react.useRef(pendingDraftPrompt);
23092
24457
  const pendingDraftFollowUpsRef = import_react.useRef(pendingDraftFollowUps);
23093
24458
  const pendingMessagesRef = import_react.useRef(pendingMessages);
23094
24459
  const awaitingFirstTurnRef = import_react.useRef(awaitingFirstTurn);
23095
24460
  const awaitingFirstTurnAssistantOutputSignatureRef = import_react.useRef(null);
23096
24461
  const sessionSnapshot = usePromptMutationSessionSnapshot(sessionStore);
23097
- const sessionStreamingRef = import_react.useRef(sessionSnapshot.streaming);
23098
24462
  const flushPendingDraftPromptRef = import_react.useRef(async () => false);
23099
24463
  import_react.useEffect(() => {
23100
24464
  draftSessionLoadingOwnerKeyRef.current = draftSessionLoadingOwnerKey;
@@ -23111,9 +24475,6 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23111
24475
  import_react.useEffect(() => {
23112
24476
  awaitingFirstTurnRef.current = awaitingFirstTurn;
23113
24477
  }, [awaitingFirstTurn]);
23114
- import_react.useEffect(() => {
23115
- sessionStreamingRef.current = sessionSnapshot.streaming;
23116
- }, [sessionSnapshot.streaming]);
23117
24478
  const updatePendingMessages = import_react.useCallback((updater) => {
23118
24479
  setPendingMessages((current) => {
23119
24480
  const next = updater(current);
@@ -23168,6 +24529,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23168
24529
  removeOptimisticUserMessage(nextPrompt.optimisticId);
23169
24530
  removeOptimisticSidebarSession(nextPrompt.optimisticSidebarSessionId);
23170
24531
  for (const followUp of pendingDraftFollowUpsRef.current) removeOptimisticUserMessage(followUp.optimisticId);
24532
+ pendingNewSessionPromptTargetRef.current = null;
23171
24533
  pendingDraftPromptRef.current = null;
23172
24534
  pendingDraftFollowUpsRef.current = [];
23173
24535
  setPendingDraftPrompt(null);
@@ -23240,17 +24602,32 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23240
24602
  const ownerKey = promptDraftKey({ cwd: nextCwd });
23241
24603
  draftSessionLoadingOwnerKeyRef.current = ownerKey;
23242
24604
  setDraftSessionLoadingOwnerKey(ownerKey);
24605
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
24606
+ awaitingFirstTurnRef.current = false;
24607
+ setAwaitingFirstTurn(false);
24608
+ pendingMessagesRef.current = [];
24609
+ setPendingMessages((current) => current.length === 0 ? current : []);
24610
+ const requestNonce = newSessionRequestNonceRef.current + 1;
24611
+ newSessionRequestNonceRef.current = requestNonce;
23243
24612
  try {
23244
- await createSessionRequest({ cwd: nextCwd });
24613
+ const response = await createSessionRequest({ cwd: nextCwd });
24614
+ if (newSessionRequestNonceRef.current !== requestNonce) return true;
24615
+ pendingNewSessionPromptTargetRef.current = {
24616
+ ownerKey,
24617
+ sessionKey: response.sessionKey,
24618
+ cwd: response.cwd
24619
+ };
23245
24620
  if (draftSessionLoadingOwnerKeyRef.current === ownerKey) {
23246
24621
  draftSessionLoadingOwnerKeyRef.current = null;
23247
24622
  setDraftSessionLoadingOwnerKey((current) => current === ownerKey ? null : current);
23248
24623
  }
23249
- if (pendingDraftPromptRef.current?.ownerKey === ownerKey) flushPendingDraftPromptRef.current(ownerKey);
24624
+ if (pendingDraftPromptRef.current?.ownerKey === ownerKey) flushPendingDraftPromptRef.current(ownerKey, response.sessionKey);
23250
24625
  return true;
23251
24626
  } catch (error) {
24627
+ if (newSessionRequestNonceRef.current !== requestNonce) return true;
23252
24628
  if (draftSessionLoadingOwnerKeyRef.current === ownerKey) draftSessionLoadingOwnerKeyRef.current = null;
23253
24629
  setDraftSessionLoadingOwnerKey((current) => current === ownerKey ? null : current);
24630
+ if (pendingNewSessionPromptTargetRef.current?.ownerKey === ownerKey) pendingNewSessionPromptTargetRef.current = null;
23254
24631
  restorePendingDraftPrompt(ownerKey);
23255
24632
  toast.error(error instanceof Error ? error.message : "Failed to create session");
23256
24633
  return false;
@@ -23261,12 +24638,14 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23261
24638
  rememberRecentDirectory,
23262
24639
  restorePendingDraftPrompt,
23263
24640
  sessionStateRef,
24641
+ setAwaitingFirstTurn,
23264
24642
  setDraftSessionLoadingOwnerKey,
24643
+ setPendingMessages,
23265
24644
  setStoredDraftDirectory,
23266
24645
  viewerContextId
23267
24646
  ]);
23268
- const queuePendingDraftPrompt = import_react.useCallback((streamingBehavior) => {
23269
- const ownerKey = draftSessionLoadingOwnerKeyRef.current;
24647
+ const queuePendingDraftPrompt = import_react.useCallback((streamingBehavior, ownerKeyOverride) => {
24648
+ const ownerKey = ownerKeyOverride || draftSessionLoadingOwnerKeyRef.current;
23270
24649
  if (!ownerKey) return false;
23271
24650
  const message = buildComposerPromptMessage({
23272
24651
  text: composerTextRef.current,
@@ -23282,6 +24661,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23282
24661
  ownerKey,
23283
24662
  message,
23284
24663
  images,
24664
+ streamingBehavior,
23285
24665
  optimisticId: addOptimisticUserMessage({
23286
24666
  message,
23287
24667
  images,
@@ -23332,12 +24712,13 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23332
24712
  updateOptimisticSidebarSessionPreview
23333
24713
  ]);
23334
24714
  const promptMutation = useMutation({
23335
- mutationFn: async ({ message, images, streamingBehavior, pendingId, thinkingLevel }) => {
24715
+ mutationFn: async ({ message, images, streamingBehavior, pendingId, clientRequestId, thinkingLevel, targetSessionKey, draftTarget }) => {
23336
24716
  if (!viewerContextId) throw new Error("Viewer context unavailable");
23337
24717
  return await fetchJson(buildCurrentSessionRequestUrl("/api/prompt", {
23338
24718
  contextId: viewerContextId,
23339
24719
  sessionState: sessionStateRef.current,
23340
- fallbackSessionId: activeSessionId
24720
+ fallbackSessionId: activeSessionId,
24721
+ targetSessionKey
23341
24722
  }), {
23342
24723
  method: "POST",
23343
24724
  headers: { "content-type": "application/json" },
@@ -23346,7 +24727,10 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23346
24727
  images,
23347
24728
  streamingBehavior,
23348
24729
  pendingId,
23349
- thinkingLevel
24730
+ clientRequestId,
24731
+ thinkingLevel,
24732
+ draftOwnerKey: draftTarget?.ownerKey,
24733
+ draftCwd: draftTarget?.cwd
23350
24734
  })
23351
24735
  });
23352
24736
  },
@@ -23357,6 +24741,58 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23357
24741
  });
23358
24742
  }
23359
24743
  });
24744
+ const offlinePromptFlushInFlightRef = import_react.useRef(false);
24745
+ const offlinePromptFailureToastIdsRef = import_react.useRef(/* @__PURE__ */ new Set());
24746
+ const flushOfflinePromptQueue = import_react.useCallback(async () => {
24747
+ if (!viewerContextId || offlinePromptFlushInFlightRef.current) return;
24748
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return;
24749
+ offlinePromptFlushInFlightRef.current = true;
24750
+ try {
24751
+ const queuedPrompts = await readOfflinePrompts(viewerContextId);
24752
+ if (queuedPrompts.length === 0) return;
24753
+ let flushedCount = 0;
24754
+ for (const item of queuedPrompts) try {
24755
+ await fetchJson(buildOfflinePromptRequestUrl(item), {
24756
+ method: "POST",
24757
+ headers: { "content-type": "application/json" },
24758
+ body: JSON.stringify(buildOfflinePromptBody(item))
24759
+ });
24760
+ await removeOfflinePrompt(item.id);
24761
+ offlinePromptFailureToastIdsRef.current.delete(item.id);
24762
+ flushedCount += 1;
24763
+ } catch (error) {
24764
+ if (isOfflinePromptSubmissionError(error)) break;
24765
+ if (!offlinePromptFailureToastIdsRef.current.has(item.id)) {
24766
+ offlinePromptFailureToastIdsRef.current.add(item.id);
24767
+ toast.error(error instanceof Error ? `Failed to send queued prompt: ${error.message}` : "Failed to send queued prompt");
24768
+ }
24769
+ break;
24770
+ }
24771
+ if (flushedCount > 0) {
24772
+ toast.success(flushedCount === 1 ? "Sent queued prompt" : `Sent ${flushedCount.toLocaleString()} queued prompts`);
24773
+ await queryClient.invalidateQueries({ refetchType: "active" });
24774
+ }
24775
+ } finally {
24776
+ offlinePromptFlushInFlightRef.current = false;
24777
+ }
24778
+ }, [queryClient, viewerContextId]);
24779
+ import_react.useEffect(() => {
24780
+ if (!viewerContextId) return;
24781
+ const flush = () => {
24782
+ flushOfflinePromptQueue();
24783
+ };
24784
+ window.addEventListener("online", flush);
24785
+ window.addEventListener("focus", flush);
24786
+ document.addEventListener("visibilitychange", flush);
24787
+ const timer = window.setInterval(flush, 3e4);
24788
+ flush();
24789
+ return () => {
24790
+ window.removeEventListener("online", flush);
24791
+ window.removeEventListener("focus", flush);
24792
+ document.removeEventListener("visibilitychange", flush);
24793
+ window.clearInterval(timer);
24794
+ };
24795
+ }, [flushOfflinePromptQueue, viewerContextId]);
23360
24796
  const submitPrompt = import_react.useCallback(async (streamingBehavior, options) => {
23361
24797
  if (!viewerContextId) return false;
23362
24798
  if (draftSessionLoadingOwnerKeyRef.current) return queuePendingDraftPrompt(streamingBehavior);
@@ -23367,13 +24803,29 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23367
24803
  diffLineComments: submittedDiffLineComments
23368
24804
  }).trim();
23369
24805
  if (!message && composerImagesRef.current.length === 0) return false;
23370
- const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(sessionStreamingRef.current || awaitingFirstTurnRef.current);
24806
+ const currentSessionState = sessionStateRef.current;
24807
+ const pendingNewSessionPromptTarget = pendingNewSessionPromptTargetRef.current;
24808
+ const currentDraftOwnerKey = currentSessionState.draft ? promptDraftKey(currentSessionState) : "";
24809
+ const implicitTargetSessionKey = !options?.targetSessionKey && currentSessionState.draft && pendingNewSessionPromptTarget && promptDraftKeyMatchesOwner(currentDraftOwnerKey, pendingNewSessionPromptTarget.ownerKey) ? pendingNewSessionPromptTarget.sessionKey : void 0;
24810
+ const targetSessionKey = options?.targetSessionKey || implicitTargetSessionKey;
24811
+ const targetDraft = currentSessionState.draft ? {
24812
+ ownerKey: options?.targetDraftOwnerKey || (targetSessionKey && pendingNewSessionPromptTarget?.sessionKey === targetSessionKey ? pendingNewSessionPromptTarget.ownerKey : currentDraftOwnerKey),
24813
+ cwd: options?.targetDraftCwd || (targetSessionKey && pendingNewSessionPromptTarget?.sessionKey === targetSessionKey ? pendingNewSessionPromptTarget.cwd || currentSessionState.cwd : currentSessionState.cwd)
24814
+ } : void 0;
24815
+ if (currentSessionState.draft && currentSessionState.sessionKey?.startsWith("optimistic:") && !targetSessionKey) {
24816
+ if (!draftSessionLoadingOwnerKeyRef.current && currentDraftOwnerKey) {
24817
+ draftSessionLoadingOwnerKeyRef.current = currentDraftOwnerKey;
24818
+ setDraftSessionLoadingOwnerKey(currentDraftOwnerKey);
24819
+ }
24820
+ return queuePendingDraftPrompt(streamingBehavior, currentDraftOwnerKey);
24821
+ }
24822
+ const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(currentSessionState.streaming || currentSessionState.compacting || awaitingFirstTurnRef.current);
23371
24823
  const normalizedStreamingBehavior = treatAsQueuedPrompt ? normalizeQueuedStreamingBehavior(streamingBehavior) : void 0;
23372
24824
  const submittedImages = composerImagesRef.current.map((image) => ({ ...image }));
23373
24825
  const sidebarPreview = promptSidebarPreview(message, submittedImages);
23374
24826
  const queuedPendingId = treatAsQueuedPrompt ? createLocalPendingId() : void 0;
23375
24827
  if (!treatAsQueuedPrompt) {
23376
- awaitingFirstTurnAssistantOutputSignatureRef.current = sessionAssistantOutputSignature(sessionStateRef.current.items);
24828
+ awaitingFirstTurnAssistantOutputSignatureRef.current = sessionAssistantOutputSignature(currentSessionState.items);
23377
24829
  awaitingFirstTurnRef.current = true;
23378
24830
  setAwaitingFirstTurn(true);
23379
24831
  }
@@ -23382,7 +24834,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23382
24834
  images: submittedImages,
23383
24835
  queued: false
23384
24836
  }));
23385
- const optimisticSidebarSessionId = options?.optimisticSidebarSessionId ?? (!treatAsQueuedPrompt && sessionStateRef.current.draft ? addOptimisticSidebarSession({ lastMessagePreview: sidebarPreview }) : void 0);
24837
+ const optimisticSidebarSessionId = options?.optimisticSidebarSessionId ?? (!treatAsQueuedPrompt && currentSessionState.draft ? addOptimisticSidebarSession({ lastMessagePreview: sidebarPreview }) : void 0);
23386
24838
  updateOptimisticSidebarSessionPreview({
23387
24839
  ...optimisticSidebarSessionId ? { optimisticSidebarSessionId } : {},
23388
24840
  preview: sidebarPreview
@@ -23393,6 +24845,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23393
24845
  images: submittedImages.map((image) => ({ ...image })),
23394
24846
  streamingBehavior: normalizedStreamingBehavior
23395
24847
  });
24848
+ const clientRequestId = createClientPromptRequestId();
23396
24849
  setIsSubmitting(true);
23397
24850
  replaceComposerDraft("", void 0, { forceSync: true });
23398
24851
  setComposerDiffLineComments([]);
@@ -23404,11 +24857,48 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23404
24857
  images: submittedImages,
23405
24858
  streamingBehavior: normalizedStreamingBehavior,
23406
24859
  pendingId: queuedPendingId,
23407
- thinkingLevel: sessionStateRef.current.thinkingLevel
24860
+ clientRequestId,
24861
+ thinkingLevel: currentSessionState.thinkingLevel,
24862
+ targetSessionKey,
24863
+ draftTarget: targetDraft
23408
24864
  });
23409
24865
  if (queuedPendingId && "queued" in response && (!response.queued || response.canceled)) removeOptimisticPendingMessage(queuedPendingId);
24866
+ if (targetSessionKey && pendingNewSessionPromptTargetRef.current?.sessionKey === targetSessionKey) pendingNewSessionPromptTargetRef.current = null;
23410
24867
  return true;
23411
24868
  } catch (error) {
24869
+ const queueSessionKey = targetSessionKey || (currentSessionState.sessionKey && !currentSessionState.sessionKey.startsWith("optimistic:") && (currentSessionState.draft || !currentSessionState.sessionId) ? currentSessionState.sessionKey : void 0);
24870
+ const queueSessionId = queueSessionKey ? void 0 : currentSessionState.sessionId ?? activeSessionId;
24871
+ if (isOfflinePromptSubmissionError(error) && (queueSessionKey || queueSessionId)) {
24872
+ await enqueueOfflinePrompt({
24873
+ id: clientRequestId,
24874
+ contextId: viewerContextId,
24875
+ createdAt: Date.now(),
24876
+ sessionId: queueSessionId,
24877
+ targetSessionKey: queueSessionKey,
24878
+ message,
24879
+ images: submittedImages.map((image) => ({
24880
+ type: "image",
24881
+ mimeType: image.mimeType,
24882
+ data: image.data
24883
+ })),
24884
+ streamingBehavior: normalizedStreamingBehavior,
24885
+ pendingId: queuedPendingId,
24886
+ clientRequestId,
24887
+ thinkingLevel: currentSessionState.thinkingLevel,
24888
+ draftOwnerKey: targetDraft?.ownerKey,
24889
+ draftCwd: targetDraft?.cwd
24890
+ });
24891
+ if (queuedPendingId) removeOptimisticPendingMessage(queuedPendingId);
24892
+ removeOptimisticUserMessage(optimisticId);
24893
+ removeOptimisticSidebarSession(optimisticSidebarSessionId);
24894
+ if (!treatAsQueuedPrompt) {
24895
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
24896
+ awaitingFirstTurnRef.current = false;
24897
+ setAwaitingFirstTurn(false);
24898
+ }
24899
+ toast.info("Prompt queued locally and will send when reconnected");
24900
+ return true;
24901
+ }
23412
24902
  if (queuedPendingId) removeOptimisticPendingMessage(queuedPendingId);
23413
24903
  removeOptimisticUserMessage(optimisticId);
23414
24904
  removeOptimisticSidebarSession(optimisticSidebarSessionId);
@@ -23431,6 +24921,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23431
24921
  setIsSubmitting(false);
23432
24922
  }
23433
24923
  }, [
24924
+ activeSessionId,
23434
24925
  addOptimisticPendingMessage,
23435
24926
  addOptimisticSidebarSession,
23436
24927
  addOptimisticUserMessage,
@@ -23449,11 +24940,12 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23449
24940
  setAwaitingFirstTurn,
23450
24941
  setComposerDiffLineComments,
23451
24942
  setComposerImages,
24943
+ setDraftSessionLoadingOwnerKey,
23452
24944
  setIsSubmitting,
23453
24945
  updateOptimisticSidebarSessionPreview,
23454
24946
  viewerContextId
23455
24947
  ]);
23456
- const flushPendingDraftFollowUps = import_react.useCallback(async () => {
24948
+ const flushPendingDraftFollowUps = import_react.useCallback(async (targetSessionKey, draftTarget) => {
23457
24949
  if (draftSessionLoadingOwnerKeyRef.current) return false;
23458
24950
  const pendingFollowUps = pendingDraftFollowUpsRef.current;
23459
24951
  if (pendingFollowUps.length === 0) return false;
@@ -23474,7 +24966,10 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23474
24966
  images: followUp.images,
23475
24967
  streamingBehavior: followUp.streamingBehavior,
23476
24968
  pendingId: followUp.optimisticId,
23477
- thinkingLevel: sessionStateRef.current.thinkingLevel
24969
+ clientRequestId: createClientPromptRequestId(),
24970
+ thinkingLevel: sessionStateRef.current.thinkingLevel,
24971
+ targetSessionKey,
24972
+ draftTarget
23478
24973
  });
23479
24974
  } catch (error) {
23480
24975
  for (const unsentFollowUp of followUps.slice(index)) removeOptimisticUserMessage(unsentFollowUp.optimisticId);
@@ -23497,24 +24992,35 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23497
24992
  setComposerImages,
23498
24993
  setPendingDraftFollowUps
23499
24994
  ]);
23500
- const flushPendingDraftPrompt = import_react.useCallback(async (ownerKey) => {
24995
+ const flushPendingDraftPrompt = import_react.useCallback(async (ownerKey, targetSessionKey) => {
23501
24996
  const nextPrompt = pendingDraftPromptRef.current;
23502
24997
  if (!nextPrompt || nextPrompt.ownerKey !== ownerKey || draftSessionLoadingOwnerKeyRef.current) return false;
24998
+ const pendingTarget = pendingNewSessionPromptTargetRef.current;
24999
+ const resolvedTargetSessionKey = targetSessionKey || (pendingTarget?.ownerKey === ownerKey ? pendingTarget.sessionKey : void 0);
25000
+ const resolvedDraftTarget = {
25001
+ ownerKey,
25002
+ cwd: pendingTarget?.cwd || sessionStateRef.current.cwd
25003
+ };
23503
25004
  pendingDraftPromptRef.current = null;
23504
25005
  setPendingDraftPrompt(null);
23505
25006
  applyPendingDraftPromptToComposer(nextPrompt);
23506
25007
  if (!await submitPrompt(nextPrompt.streamingBehavior, {
23507
25008
  forceFirstPrompt: true,
23508
25009
  optimisticId: nextPrompt.optimisticId,
23509
- optimisticSidebarSessionId: nextPrompt.optimisticSidebarSessionId
25010
+ optimisticSidebarSessionId: nextPrompt.optimisticSidebarSessionId,
25011
+ targetSessionKey: resolvedTargetSessionKey,
25012
+ targetDraftOwnerKey: resolvedDraftTarget.ownerKey,
25013
+ targetDraftCwd: resolvedDraftTarget.cwd
23510
25014
  })) {
23511
25015
  removeOptimisticSidebarSession(nextPrompt.optimisticSidebarSessionId);
23512
25016
  for (const followUp of pendingDraftFollowUpsRef.current) removeOptimisticUserMessage(followUp.optimisticId);
23513
25017
  pendingDraftFollowUpsRef.current = [];
23514
25018
  setPendingDraftFollowUps([]);
25019
+ if (pendingNewSessionPromptTargetRef.current?.ownerKey === ownerKey) pendingNewSessionPromptTargetRef.current = null;
23515
25020
  return false;
23516
25021
  }
23517
- await flushPendingDraftFollowUps();
25022
+ await flushPendingDraftFollowUps(resolvedTargetSessionKey, resolvedDraftTarget);
25023
+ if (pendingNewSessionPromptTargetRef.current?.ownerKey === ownerKey) pendingNewSessionPromptTargetRef.current = null;
23518
25024
  return true;
23519
25025
  }, [
23520
25026
  applyPendingDraftPromptToComposer,
@@ -23532,9 +25038,15 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23532
25038
  if (currentSessionState.sessionKey?.startsWith("optimistic:")) return;
23533
25039
  const currentOwnerKey = promptDraftKey(currentSessionState);
23534
25040
  if (!currentSessionState.draft || !promptDraftKeyMatchesOwner(currentOwnerKey, draftSessionLoadingOwnerKey)) return;
25041
+ const targetSessionKey = currentSessionState.sessionKey?.startsWith("optimistic:") ? void 0 : currentSessionState.sessionKey;
25042
+ if (targetSessionKey) pendingNewSessionPromptTargetRef.current = {
25043
+ ownerKey: draftSessionLoadingOwnerKey,
25044
+ sessionKey: targetSessionKey,
25045
+ cwd: currentSessionState.cwd
25046
+ };
23535
25047
  draftSessionLoadingOwnerKeyRef.current = null;
23536
25048
  setDraftSessionLoadingOwnerKey(null);
23537
- if (pendingDraftPromptRef.current?.ownerKey === draftSessionLoadingOwnerKey) flushPendingDraftPrompt(draftSessionLoadingOwnerKey);
25049
+ if (pendingDraftPromptRef.current?.ownerKey === draftSessionLoadingOwnerKey) flushPendingDraftPrompt(draftSessionLoadingOwnerKey, targetSessionKey);
23538
25050
  }, [
23539
25051
  draftSessionLoadingOwnerKey,
23540
25052
  flushPendingDraftPrompt,
@@ -23632,6 +25144,41 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23632
25144
  updatePendingMessages,
23633
25145
  viewerContextId
23634
25146
  ]);
25147
+ const startPendingQueueMutation = useMutation({
25148
+ mutationFn: async () => {
25149
+ if (!viewerContextId) throw new Error("Viewer context unavailable");
25150
+ return await fetchJson(buildCurrentSessionRequestUrl("/api/pending-messages/start", {
25151
+ contextId: viewerContextId,
25152
+ sessionState: sessionStateRef.current,
25153
+ fallbackSessionId: activeSessionId
25154
+ }), { method: "POST" });
25155
+ },
25156
+ onSuccess: async () => {
25157
+ await queryClient.invalidateQueries({
25158
+ queryKey: picoQueryKeys.sessionTree(viewerContextId, picoSessionScopeKey(sessionStateRef.current)),
25159
+ refetchType: "active"
25160
+ });
25161
+ }
25162
+ });
25163
+ const startPendingQueue = import_react.useCallback(async () => {
25164
+ if (!viewerContextId) return;
25165
+ if (pendingMessagesRef.current.length === 0) return;
25166
+ setIsSubmitting(true);
25167
+ try {
25168
+ const nextPendingMessages = pendingMessagesFromResponse(await startPendingQueueMutation.mutateAsync());
25169
+ if (nextPendingMessages) updatePendingMessages(() => nextPendingMessages);
25170
+ } catch (error) {
25171
+ toast.error(error instanceof Error ? error.message : "Failed to start queue");
25172
+ } finally {
25173
+ setIsSubmitting(false);
25174
+ }
25175
+ }, [
25176
+ pendingMessagesRef,
25177
+ setIsSubmitting,
25178
+ startPendingQueueMutation,
25179
+ updatePendingMessages,
25180
+ viewerContextId
25181
+ ]);
23635
25182
  const removePendingMessageMutation = useMutation({
23636
25183
  mutationFn: async (pendingId) => {
23637
25184
  if (!viewerContextId) throw new Error("Viewer context unavailable");
@@ -23693,6 +25240,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
23693
25240
  updatePendingMessages,
23694
25241
  viewerContextId
23695
25242
  ]),
25243
+ startPendingQueue,
23696
25244
  submitPrompt
23697
25245
  };
23698
25246
  }
@@ -24294,7 +25842,7 @@ function useAppShellSessionMutations({ viewerContextId, activeSessionId, session
24294
25842
  });
24295
25843
  return {
24296
25844
  cycleThinkingLevel,
24297
- deleteSessions: import_react.useCallback(async (targets) => {
25845
+ deleteSessions: import_react.useCallback((targets) => {
24298
25846
  if (!viewerContextId || targets.length === 0) return false;
24299
25847
  const orderedTargets = [...targets.filter((target) => target.path && target.path !== sessionStateRef.current.sessionFile), ...targets.filter((target) => target.path && target.path === sessionStateRef.current.sessionFile)];
24300
25848
  const orderedPaths = [];
@@ -24363,6 +25911,26 @@ function useAppShellSessionMutations({ viewerContextId, activeSessionId, session
24363
25911
  }
24364
25912
  var RESUME_RECONNECT_AFTER_MS = 3e4;
24365
25913
  var COMPACT_WORKING_LABEL = "Compacting context...";
25914
+ var EVENT_SOURCE_LAST_ID_STORAGE_PREFIX = "pico-events-last-id:";
25915
+ function eventsLastIdStorageKey(viewerContextId) {
25916
+ return `${EVENT_SOURCE_LAST_ID_STORAGE_PREFIX}${viewerContextId}`;
25917
+ }
25918
+ function readStoredEventsLastId(viewerContextId) {
25919
+ try {
25920
+ return window.sessionStorage.getItem(eventsLastIdStorageKey(viewerContextId));
25921
+ } catch {
25922
+ return null;
25923
+ }
25924
+ }
25925
+ function rememberStoredEventsLastId(viewerContextId, lastEventId) {
25926
+ try {
25927
+ window.sessionStorage.setItem(eventsLastIdStorageKey(viewerContextId), lastEventId);
25928
+ } catch {}
25929
+ }
25930
+ function normalizeEventSourceLastId(value) {
25931
+ const normalized = typeof value === "string" ? value.trim() : "";
25932
+ return /^\d+$/.test(normalized) ? normalized : void 0;
25933
+ }
24366
25934
  function normalizePendingMessages(payload) {
24367
25935
  if (!isStateSyncEvent(payload)) return;
24368
25936
  if (!Array.isArray(payload.pendingUserMessages)) return;
@@ -24376,6 +25944,9 @@ function normalizePendingMessages(payload) {
24376
25944
  streamingBehavior: message.streamingBehavior === "steer" ? "steer" : "followUp"
24377
25945
  }));
24378
25946
  }
25947
+ function autoSessionNamingErrorDescription(payload) {
25948
+ return payload.refinementReason || payload.heuristicReason || payload.promptPreview || "The server could not generate a session name.";
25949
+ }
24379
25950
  function sameStringArray(left = [], right = []) {
24380
25951
  if (left.length !== right.length) return false;
24381
25952
  for (let index = 0; index < left.length; index += 1) if (left[index] !== right[index]) return false;
@@ -24463,7 +26034,7 @@ function sameSessionsEvent(left, right) {
24463
26034
  function visibleAssistantBlockKey(block, options) {
24464
26035
  switch (block.type) {
24465
26036
  case "text": return block.text.trim() ? `text:${block.text}` : "";
24466
- case "compaction": return `compaction:${block.tokensBefore}:${block.summary}`;
26037
+ case "compaction": return `compaction:${block.tokensBefore}:${block.estimatedTokensAfter ?? ""}:${block.summary}`;
24467
26038
  case "thinking": return options.hideThinking ? "" : `thinking:${block.summaryLabel || ""}:${block.text}`;
24468
26039
  case "tool": return options.hideToolBlocks ? "" : `tool:${block.callId || ""}:${block.name || ""}:${JSON.stringify(block.args ?? null)}:${block.output}:${JSON.stringify(block.details ?? null)}:${block.isError ? "1" : "0"}:${block.running ? "1" : "0"}`;
24469
26040
  default: return "";
@@ -24540,6 +26111,7 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24540
26111
  const currentSessionIdRef = import_react.useRef(sessionId);
24541
26112
  const draftSessionLoadingOwnerKeyRef = import_react.useRef(draftSessionLoadingOwnerKey);
24542
26113
  const currentSourceRef = import_react.useRef(null);
26114
+ const eventsLastIdRef = import_react.useRef(void 0);
24543
26115
  const hasReceivedStateSyncRef = import_react.useRef(false);
24544
26116
  const [hasReceivedStateSync, setHasReceivedStateSync] = import_react.useState(false);
24545
26117
  const backgroundedAtRef = import_react.useRef(null);
@@ -24551,6 +26123,9 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24551
26123
  import_react.useEffect(() => {
24552
26124
  draftSessionLoadingOwnerKeyRef.current = draftSessionLoadingOwnerKey;
24553
26125
  }, [draftSessionLoadingOwnerKey]);
26126
+ import_react.useEffect(() => {
26127
+ eventsLastIdRef.current = normalizeEventSourceLastId(readStoredEventsLastId(viewerContextId));
26128
+ }, [viewerContextId]);
24554
26129
  import_react.useEffect(() => {
24555
26130
  if (currentSourceRef.current) return;
24556
26131
  initialEventsSessionIdRef.current = sessionId;
@@ -24666,6 +26241,11 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24666
26241
  ]);
24667
26242
  const handleEventSourceMessage = import_react.useCallback((source, gitInvalidationBatcher, event) => {
24668
26243
  if (currentSourceRef.current !== source) return;
26244
+ const lastEventId = normalizeEventSourceLastId(event.lastEventId);
26245
+ if (lastEventId) {
26246
+ eventsLastIdRef.current = lastEventId;
26247
+ rememberStoredEventsLastId(viewerContextId, lastEventId);
26248
+ }
24669
26249
  const payload = JSON.parse(event.data);
24670
26250
  if (isStateSyncEvent(payload)) {
24671
26251
  const activationRevision = payload.activationRevision;
@@ -24764,6 +26344,11 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24764
26344
  });
24765
26345
  return;
24766
26346
  }
26347
+ if (payload.type === "auto_session_naming_error") {
26348
+ console.warn("[pico] auto session naming failed", payload);
26349
+ toast.warning("Could not name session", { description: autoSessionNamingErrorDescription(payload) });
26350
+ return;
26351
+ }
24767
26352
  if (payload.type === "request_error") {
24768
26353
  toast.error(payload.error || "Request failed");
24769
26354
  return;
@@ -24804,7 +26389,8 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24804
26389
  setSessionDoneEvents,
24805
26390
  setSessionState,
24806
26391
  setSessionsEvent,
24807
- setWorkingState
26392
+ setWorkingState,
26393
+ viewerContextId
24808
26394
  ]);
24809
26395
  import_react.useEffect(() => {
24810
26396
  if (!viewerContextId) return;
@@ -24856,7 +26442,10 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24856
26442
  const source = new EventSource(buildRequestUrl("/events", {
24857
26443
  contextId: viewerContextId,
24858
26444
  sessionId: initialEventsSessionIdRef.current,
24859
- searchParams: { sidebarDirectory: bootstrapSidebarDirectories }
26445
+ searchParams: {
26446
+ lastEventId: eventsLastIdRef.current,
26447
+ sidebarDirectory: bootstrapSidebarDirectories
26448
+ }
24860
26449
  }));
24861
26450
  currentSourceRef.current = source;
24862
26451
  source.onopen = () => {
@@ -24941,10 +26530,13 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
24941
26530
  ]);
24942
26531
  }
24943
26532
  var DOUBLE_ESCAPE_INTERVAL_MS = 500;
26533
+ function isTerminalTarget(target) {
26534
+ return target instanceof HTMLElement && target.closest("[data-terminal-panel=\"true\"]") !== null;
26535
+ }
24944
26536
  function isEditableTarget(target) {
24945
26537
  if (!(target instanceof HTMLElement)) return false;
24946
26538
  const tagName = target.tagName.toLowerCase();
24947
- return target.isContentEditable || tagName === "input" || tagName === "textarea" || target.closest("[contenteditable=\"true\"]") !== null;
26539
+ return target.isContentEditable || isTerminalTarget(target) || tagName === "input" || tagName === "textarea" || target.closest("[contenteditable=\"true\"]") !== null;
24948
26540
  }
24949
26541
  function isPromptTextareaTarget(target) {
24950
26542
  return target instanceof HTMLTextAreaElement && target.name === "prompt";
@@ -24984,7 +26576,7 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
24984
26576
  };
24985
26577
  const handleEscape = (event) => {
24986
26578
  const context = getShortcutContext(event);
24987
- if (event.repeat || context.modalOpen || event.defaultPrevented) return;
26579
+ if (event.repeat || context.modalOpen || event.defaultPrevented || isTerminalTarget(event.target)) return;
24988
26580
  const now = Date.now();
24989
26581
  if (now - lastEscapeKeyDownAtRef.current <= DOUBLE_ESCAPE_INTERVAL_MS) {
24990
26582
  lastEscapeKeyDownAtRef.current = 0;
@@ -25023,7 +26615,7 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
25023
26615
  };
25024
26616
  const handleFocusPrompt = (event) => {
25025
26617
  const context = getShortcutContext(event);
25026
- if (context.blockingModalOpen || event.defaultPrevented) return;
26618
+ if (context.blockingModalOpen || event.defaultPrevented || isTerminalTarget(event.target)) return;
25027
26619
  event.preventDefault();
25028
26620
  closeCommandPaletteForShortcut(context.commandPaletteOpen);
25029
26621
  shortcutActionsRef.current.focusPrompt();
@@ -25050,12 +26642,19 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
25050
26642
  const key = event.altKey && event.code.startsWith("Key") ? event.code.slice(3).toLowerCase() : event.key.toLowerCase();
25051
26643
  const context = getShortcutContext(event);
25052
26644
  if (context.blockingModalOpen || event.defaultPrevented) return;
26645
+ if (isTerminalTarget(event.target) && !context.commandPaletteOpen && key !== "`") return;
25053
26646
  if (key === "\\") {
25054
26647
  event.preventDefault();
25055
26648
  closeCommandPaletteForShortcut(context.commandPaletteOpen);
25056
26649
  shortcutActionsRef.current.toggleGitPanel();
25057
26650
  return;
25058
26651
  }
26652
+ if (key === "`") {
26653
+ event.preventDefault();
26654
+ closeCommandPaletteForShortcut(context.commandPaletteOpen);
26655
+ shortcutActionsRef.current.toggleTerminalPanel();
26656
+ return;
26657
+ }
25059
26658
  if (key === "k") {
25060
26659
  event.preventDefault();
25061
26660
  shortcutActionsRef.current.openCommandPalette();
@@ -25227,6 +26826,7 @@ function useAppShellShortcuts({ addDirectoryOpenRef, commandPaletteOpenRef, comp
25227
26826
  })),
25228
26827
  ...[
25229
26828
  { key: "\\" },
26829
+ { key: "`" },
25230
26830
  { key: "K" },
25231
26831
  { key: "P" },
25232
26832
  {
@@ -25295,6 +26895,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25295
26895
  if (!appUiStoreRef.current) appUiStoreRef.current = createPicoStore({
25296
26896
  currentTab: "session",
25297
26897
  gitPanelOpen: false,
26898
+ terminalPanelOpen: false,
25298
26899
  initialLoadingSessionId: sessionId || null,
25299
26900
  loadingSessionId: null
25300
26901
  }, shallowRecordEqual);
@@ -25310,6 +26911,11 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25310
26911
  setStoreField(appUiStore, "gitPanelOpen", nextOpen);
25311
26912
  safeLocalStorageSetItem(RIGHT_SIDEBAR_OPEN_STORAGE_KEY, nextOpen ? "1" : "0");
25312
26913
  }, [appUiStore]);
26914
+ const setTerminalPanelOpen = import_react.useCallback((action) => {
26915
+ const nextOpen = applyStoreAction(appUiStore.state.terminalPanelOpen, action);
26916
+ setStoreField(appUiStore, "terminalPanelOpen", nextOpen);
26917
+ safeLocalStorageSetItem(TERMINAL_PANEL_OPEN_STORAGE_KEY, nextOpen ? "1" : "0");
26918
+ }, [appUiStore]);
25313
26919
  const setLoadingSessionId = import_react.useCallback((action) => {
25314
26920
  setStoreField(appUiStore, "loadingSessionId", action);
25315
26921
  }, [appUiStore]);
@@ -25735,6 +27341,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25735
27341
  sessionKey: currentSessionState.sessionKey
25736
27342
  }), { compare: shallowRecordEqual });
25737
27343
  const gitPanelOpen = useSelector(appUiStore, (state) => state.gitPanelOpen);
27344
+ const terminalPanelOpen = useSelector(appUiStore, (state) => state.terminalPanelOpen);
25738
27345
  const openFileViewTab = (path, options) => {
25739
27346
  if (!path) return;
25740
27347
  batch(() => {
@@ -25831,7 +27438,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25831
27438
  } : currentSession : { draft: true }, sessionId);
25832
27439
  setStoreState(appUiStore, (current) => ({
25833
27440
  ...current,
25834
- currentTab: current.currentTab === "git" ? "session" : current.currentTab,
27441
+ currentTab: current.currentTab === "git" || current.currentTab === "terminal" ? "session" : current.currentTab,
25835
27442
  initialLoadingSessionId: sessionId ? current.initialLoadingSessionId : null,
25836
27443
  loadingSessionId: sessionId && sessionStateRef.current.sessionId !== sessionId ? sessionId : null
25837
27444
  }));
@@ -25843,7 +27450,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25843
27450
  ]);
25844
27451
  import_react.useEffect(() => {
25845
27452
  if (isMobile) return;
25846
- setCurrentTab((tab) => tab === "git" ? "session" : tab);
27453
+ setCurrentTab((tab) => tab === "git" || tab === "terminal" ? "session" : tab);
25847
27454
  }, [isMobile, setCurrentTab]);
25848
27455
  const syncComposerDraft = (value, target = sessionStateRef.current) => {
25849
27456
  const parsed = parseComposerSkillMessage(value);
@@ -25887,6 +27494,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25887
27494
  const applyStoredClientPreferences = () => {
25888
27495
  batch(() => {
25889
27496
  setGitPanelOpen(readStoredRightSidebarOpen());
27497
+ setTerminalPanelOpen(readStoredTerminalPanelOpen());
25890
27498
  setStoredDraftDirectory(readStoredDraftDirectory() || "");
25891
27499
  setSessionDoneSoundEnabled(readStoredSessionDoneSoundEnabled());
25892
27500
  setSessionDoneDesktopNotificationsEnabled(readStoredSessionDoneDesktopNotificationsEnabled());
@@ -26050,12 +27658,27 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26050
27658
  }
26051
27659
  setGitPanelOpen((open) => !open);
26052
27660
  };
27661
+ const toggleTerminalPanel = () => {
27662
+ if (isMobile) {
27663
+ setCurrentTab((tab) => tab === "terminal" ? "session" : "terminal");
27664
+ return;
27665
+ }
27666
+ setTerminalPanelOpen((open) => !open);
27667
+ };
27668
+ const closeTerminalPanel = () => {
27669
+ if (isMobile) {
27670
+ setCurrentTab((tab) => tab === "terminal" ? "session" : tab);
27671
+ return;
27672
+ }
27673
+ setTerminalPanelOpen(false);
27674
+ };
26053
27675
  const openRenameDialog = () => {
26054
27676
  const currentState = sessionStateRef.current;
26055
27677
  if (!currentState.sessionFile) return;
26056
27678
  renameDialogRef.current?.open({
26057
27679
  path: currentState.sessionFile,
26058
- title: currentState.sessionName || getCurrentSessionTitleFromState(currentState)
27680
+ title: currentState.sessionName || getCurrentSessionTitleFromState(currentState),
27681
+ hasGeneratedName: Boolean(currentState.sessionName?.trim())
26059
27682
  });
26060
27683
  };
26061
27684
  const openRenameDialogForEntry = (entry) => {
@@ -26448,7 +28071,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26448
28071
  const pendingDraftPrompt = pendingDraftPromptStore.state;
26449
28072
  const pendingDraftFollowUps = pendingDraftFollowUpsStore.state;
26450
28073
  const pendingMessages = pendingMessagesStore.state;
26451
- const { abortSession, addDirectoryPath, createSession: requestCreateSession, editPendingMessage, removePendingMessage, reorderPending, submitPrompt } = useAppShellPromptMutations({
28074
+ const { abortSession, addDirectoryPath, createSession: requestCreateSession, editPendingMessage, removePendingMessage, reorderPending, startPendingQueue, submitPrompt } = useAppShellPromptMutations({
26452
28075
  viewerContextId,
26453
28076
  activeSessionId,
26454
28077
  defaultNewSessionDirectory,
@@ -26528,10 +28151,6 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26528
28151
  const createSessionPromise = requestCreateSession(nextCwd);
26529
28152
  handleSelectSession(void 0);
26530
28153
  clearSelectedSidebarSelection();
26531
- if (shouldCloseMobileSidebar) {
26532
- pendingMobileSidebarPromptFocusRef.current = true;
26533
- setOpenMobile(false);
26534
- } else focusPrompt();
26535
28154
  setAwaitingFirstTurn(false);
26536
28155
  setPendingMessages((current) => current.length === 0 ? current : []);
26537
28156
  const nextState = createOptimisticDraftSessionState({
@@ -26542,6 +28161,10 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26542
28161
  sessionStateRef.current = nextState;
26543
28162
  conversationItemsStore.setItems(nextState.items);
26544
28163
  setSessionState(nextState);
28164
+ if (shouldCloseMobileSidebar) {
28165
+ pendingMobileSidebarPromptFocusRef.current = true;
28166
+ setOpenMobile(false);
28167
+ } else focusPrompt();
26545
28168
  if (await createSessionPromise) return;
26546
28169
  if (sessionStateRef.current.sessionKey !== optimisticSessionKey) return;
26547
28170
  sessionStateRef.current = previousState;
@@ -26887,6 +28510,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26887
28510
  const composerSnapshot = {
26888
28511
  activeSessionId,
26889
28512
  awaitingFirstTurn: composerDisabled ? false : awaitingFirstTurn,
28513
+ canStartPendingQueue: !composerDisabled && pendingMessages.length > 0 && !sessionStateRef.current.streaming && !sessionStateRef.current.compacting && !awaitingFirstTurn && !isSubmitting,
26890
28514
  centerMessages: displaySettingsRef.current.centerMessages,
26891
28515
  composerDiffLineComments: displayedDiffLineComments,
26892
28516
  composerImages: displayedComposerImages,
@@ -26895,6 +28519,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26895
28519
  composerText: displayedComposerText,
26896
28520
  currentPendingMessages: displayedPendingMessages,
26897
28521
  disabled: composerDisabled,
28522
+ isCompacting: composerDisabled ? false : sessionStateRef.current.compacting,
26898
28523
  isStreaming: composerDisabled ? false : sessionStateRef.current.streaming,
26899
28524
  isSubmitting: composerDisabled ? false : isSubmitting,
26900
28525
  viewerContextId
@@ -26917,6 +28542,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26917
28542
  removePendingMessage,
26918
28543
  reorderPending,
26919
28544
  reorderPendingDraftFollowUp,
28545
+ startPendingQueue,
26920
28546
  runBuiltinSlashCommand,
26921
28547
  setModel,
26922
28548
  setThinkingLevel,
@@ -26928,7 +28554,8 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26928
28554
  hasAvailableModels: sessionStateRef.current.availableModels.length > 0,
26929
28555
  isMobile,
26930
28556
  selectedSidebarSessions,
26931
- sessionFile: sessionState.sessionFile
28557
+ sessionFile: sessionState.sessionFile,
28558
+ terminalPanelOpen
26932
28559
  });
26933
28560
  const buildCommandPaletteCommands = () => {
26934
28561
  const commandState = commandPaletteStateRef.current;
@@ -26979,6 +28606,21 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26979
28606
  ],
26980
28607
  onSelect: toggleGitPanel
26981
28608
  },
28609
+ {
28610
+ id: "toggle-terminal",
28611
+ group: "Workspace",
28612
+ title: commandState.isMobile ? "Toggle terminal tab" : commandState.terminalPanelOpen ? "Close terminal" : "Open terminal",
28613
+ description: commandState.isMobile ? "Switch the mobile terminal tab on or off" : "Toggle the bottom terminal panel",
28614
+ shortcut: formatShortcutLabel("Control+`"),
28615
+ keywords: [
28616
+ "terminal",
28617
+ "shell",
28618
+ "console",
28619
+ "bottom",
28620
+ "panel"
28621
+ ],
28622
+ onSelect: toggleTerminalPanel
28623
+ },
26982
28624
  {
26983
28625
  id: "commit-changes",
26984
28626
  group: "Git",
@@ -27343,6 +28985,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27343
28985
  conversationFrameRef.current?.scrollConversationToTop();
27344
28986
  },
27345
28987
  toggleGitPanel,
28988
+ toggleTerminalPanel,
27346
28989
  toggleHideThinking,
27347
28990
  toggleHideToolBlocks,
27348
28991
  cycleThinkingLevel
@@ -27483,12 +29126,14 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27483
29126
  defaultNewSessionDirectory,
27484
29127
  displaySessionCwd,
27485
29128
  gitPanelOpen,
29129
+ terminalPanelOpen,
27486
29130
  loadingDisplaySessionTitle,
27487
29131
  currentSessionPinned,
27488
29132
  displaySettingsStore,
27489
29133
  isSessionViewLoading,
27490
29134
  newSessionDirectoryOptions,
27491
29135
  onToggleGitPanel: toggleGitPanel,
29136
+ onToggleTerminalPanel: toggleTerminalPanel,
27492
29137
  sessionStore,
27493
29138
  viewerContextId
27494
29139
  }),
@@ -27508,6 +29153,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27508
29153
  displaySettingsStore,
27509
29154
  fileInputRef,
27510
29155
  gitPanelOpen,
29156
+ terminalPanelOpen,
27511
29157
  hiddenThinkingPreviewStore,
27512
29158
  isSessionViewLoading,
27513
29159
  isSubmitting,
@@ -27525,6 +29171,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27525
29171
  onAddDiffLineComment: addComposerDiffLineComment,
27526
29172
  onOpenFileViewTab: openFileViewTab,
27527
29173
  onReorderFileViewTabs: reorderFileViewTabs,
29174
+ onCloseTerminalPanel: closeTerminalPanel,
27528
29175
  rightSidebarStore,
27529
29176
  sessionStore,
27530
29177
  store: composerStore,