@vantran-se/openchamber-web 1.23.0 → 1.24.3

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 (362) hide show
  1. package/bin/cli.test.js +29 -6
  2. package/bin/lib/cli-http.js +5 -1
  3. package/bin/lib/commands-status.js +5 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
  5. package/dist/assets/DiffView-COVQhWGd.js +4 -0
  6. package/dist/assets/FilesView-B1V5JxFX.js +13 -0
  7. package/dist/assets/GitView-CDOJy5Ay.js +6 -0
  8. package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
  9. package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.js} +1 -1
  10. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  11. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  12. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  14. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  15. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  20. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  21. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  28. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  30. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  31. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  32. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  33. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  34. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  35. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  37. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  38. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  39. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  40. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  41. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  42. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  52. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  53. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  54. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  55. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  56. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  57. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  58. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  59. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  60. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  61. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  62. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  63. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  64. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  65. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  66. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  67. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  68. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  69. package/dist/assets/LinearIssuesView-D7FKg1XJ.js +1 -0
  70. package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
  71. package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
  72. package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
  73. package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
  74. package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
  75. package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
  76. package/dist/assets/PluginPane-B4tKno2h.js +3 -0
  77. package/dist/assets/SettingsView-DEaMulN9.js +29 -0
  78. package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
  79. package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
  80. package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
  81. package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
  82. package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
  83. package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
  84. package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
  85. package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
  86. package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
  87. package/dist/assets/de-DgxgFyGG.js +10 -0
  88. package/dist/assets/es-BFsZj5F_.js +16 -0
  89. package/dist/assets/fr-jdZ5KKQi.js +14 -0
  90. package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
  91. package/dist/assets/index-Cbci4i1W.js +40 -0
  92. package/dist/assets/index-DqaMWNrC.css +1 -0
  93. package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
  94. package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
  95. package/dist/assets/ja-C5kKrhO4.js +16 -0
  96. package/dist/assets/ko-DXDTT_0O.js +16 -0
  97. package/dist/assets/main-CZALtVek.js +896 -0
  98. package/dist/assets/main-CgXMBNvG.js +2 -0
  99. package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
  100. package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
  101. package/dist/assets/miniChat-CwTmDixi.js +2 -0
  102. package/dist/assets/mobile-CLf2vCtO.js +2 -0
  103. package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
  104. package/dist/assets/pl-D-7x4Y8Q.js +16 -0
  105. package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
  106. package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
  107. package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
  108. package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
  109. package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
  110. package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
  111. package/dist/assets/surface-B6n7wJtt.js +1 -0
  112. package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
  113. package/dist/assets/tr-Dr-oJ22L.js +7 -0
  114. package/dist/assets/uk-9Nx83UcI.js +16 -0
  115. package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
  116. package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
  117. package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
  118. package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
  119. package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
  120. package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
  121. package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
  122. package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
  123. package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
  124. package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
  125. package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
  126. package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
  127. package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
  128. package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
  129. package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
  130. package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
  131. package/dist/index.html +6 -4
  132. package/dist/mini-chat.html +6 -4
  133. package/dist/mobile.html +6 -4
  134. package/package.json +12 -9
  135. package/server/built-in-extensions/registry.json +4 -0
  136. package/server/index.d.ts +2 -0
  137. package/server/index.js +47 -23
  138. package/server/lib/agent-memory/runtime.js +7 -2
  139. package/server/lib/agent-memory/runtime.test.js +22 -0
  140. package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
  141. package/server/lib/agent-tool/runtime.js +62 -15
  142. package/server/lib/agent-tool/runtime.test.js +88 -2
  143. package/server/lib/changelog/update-notes.js +2 -1
  144. package/server/lib/changelog/update-notes.test.js +19 -0
  145. package/server/lib/event-stream/DOCUMENTATION.md +26 -0
  146. package/server/lib/event-stream/delta-coalescer.js +0 -0
  147. package/server/lib/event-stream/delta-coalescer.test.js +292 -0
  148. package/server/lib/event-stream/global-hub.js +68 -13
  149. package/server/lib/event-stream/global-hub.test.js +182 -0
  150. package/server/lib/event-stream/global-ws-bridge.js +16 -18
  151. package/server/lib/event-stream/index.js +4 -0
  152. package/server/lib/event-stream/protocol.js +21 -7
  153. package/server/lib/event-stream/runtime.js +9 -6
  154. package/server/lib/event-stream/runtime.test.js +60 -0
  155. package/server/lib/fs/DOCUMENTATION.md +10 -1
  156. package/server/lib/fs/git-process.test.js +77 -0
  157. package/server/lib/fs/routes.js +58 -5
  158. package/server/lib/fs/routes.test.js +116 -0
  159. package/server/lib/fs/search.js +2 -1
  160. package/server/lib/git/DOCUMENTATION.md +30 -6
  161. package/server/lib/git/routes.js +37 -11
  162. package/server/lib/git/routes.test.js +78 -0
  163. package/server/lib/git/serial-refresh.js +93 -0
  164. package/server/lib/git/serial-refresh.test.js +112 -0
  165. package/server/lib/git/service.js +588 -74
  166. package/server/lib/git/service.test.js +492 -0
  167. package/server/lib/github/DOCUMENTATION.md +5 -0
  168. package/server/lib/github/pr-status.js +5 -5
  169. package/server/lib/github/pr-status.test.js +1 -1
  170. package/server/lib/github/routes.js +6 -6
  171. package/server/lib/guests/DOCUMENTATION.md +68 -0
  172. package/server/lib/guests/auth-store.js +184 -0
  173. package/server/lib/guests/auth-store.test.js +112 -0
  174. package/server/lib/guests/background.test.js +83 -0
  175. package/server/lib/guests/builtins.js +34 -0
  176. package/server/lib/guests/builtins.test.js +174 -0
  177. package/server/lib/guests/catalog.js +477 -0
  178. package/server/lib/guests/catalog.test.js +390 -0
  179. package/server/lib/guests/clone.js +297 -0
  180. package/server/lib/guests/extract-zip.js +136 -0
  181. package/server/lib/guests/extract-zip.test.js +109 -0
  182. package/server/lib/guests/files.js +308 -0
  183. package/server/lib/guests/files.test.js +262 -0
  184. package/server/lib/guests/grant-scope.js +81 -0
  185. package/server/lib/guests/grant-scope.test.js +75 -0
  186. package/server/lib/guests/host-session.js +73 -0
  187. package/server/lib/guests/host-session.test.js +30 -0
  188. package/server/lib/guests/html-styles.js +6 -0
  189. package/server/lib/guests/html-styles.test.js +45 -0
  190. package/server/lib/guests/html-tokens.js +73 -0
  191. package/server/lib/guests/html-tokens.test.js +53 -0
  192. package/server/lib/guests/install.js +406 -0
  193. package/server/lib/guests/install.test.js +338 -0
  194. package/server/lib/guests/oauth.js +501 -0
  195. package/server/lib/guests/oauth.test.js +481 -0
  196. package/server/lib/guests/persist.js +336 -0
  197. package/server/lib/guests/persist.test.js +288 -0
  198. package/server/lib/guests/request.js +112 -0
  199. package/server/lib/guests/request.test.js +206 -0
  200. package/server/lib/guests/routes.js +838 -0
  201. package/server/lib/guests/service.js +538 -0
  202. package/server/lib/guests/service.test.js +277 -0
  203. package/server/lib/guests/sockets.js +129 -0
  204. package/server/lib/guests/sockets.test.js +110 -0
  205. package/server/lib/guests/ssh-install.test.js +188 -0
  206. package/server/lib/guests/storage.js +68 -0
  207. package/server/lib/guests/storage.test.js +59 -0
  208. package/server/lib/guests/updates.js +294 -0
  209. package/server/lib/guests/updates.test.js +334 -0
  210. package/server/lib/guests/upload.js +50 -0
  211. package/server/lib/guests/upload.test.js +131 -0
  212. package/server/lib/linear/DOCUMENTATION.md +1 -1
  213. package/server/lib/message-queue/DOCUMENTATION.md +17 -1
  214. package/server/lib/message-queue/runtime.js +34 -1
  215. package/server/lib/message-queue/runtime.test.js +83 -6
  216. package/server/lib/notifications/DOCUMENTATION.md +1 -1
  217. package/server/lib/notifications/emitter-runtime.js +4 -3
  218. package/server/lib/notifications/routes.js +5 -0
  219. package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
  220. package/server/lib/openchamber-sessions/routes.js +62 -29
  221. package/server/lib/openchamber-sessions/routes.test.js +96 -0
  222. package/server/lib/opencode/DOCUMENTATION.md +73 -5
  223. package/server/lib/opencode/bootstrap-runtime.js +1 -0
  224. package/server/lib/opencode/core-routes.js +39 -1
  225. package/server/lib/opencode/env-runtime.js +5 -1
  226. package/server/lib/opencode/env-runtime.test.js +72 -0
  227. package/server/lib/opencode/feature-routes-runtime.js +30 -1
  228. package/server/lib/opencode/lifecycle-process.test.js +84 -0
  229. package/server/lib/opencode/lifecycle.js +58 -70
  230. package/server/lib/opencode/lifecycle.test.js +29 -0
  231. package/server/lib/opencode/managed-plugin-config.js +1 -1
  232. package/server/lib/opencode/openchamber-routes.js +72 -14
  233. package/server/lib/opencode/openchamber-routes.test.js +71 -3
  234. package/server/lib/opencode/proxy.js +33 -0
  235. package/server/lib/opencode/session-runtime.js +68 -0
  236. package/server/lib/opencode/session-runtime.test.js +43 -0
  237. package/server/lib/opencode/settings-helpers.js +11 -0
  238. package/server/lib/opencode/settings-helpers.test.js +24 -2
  239. package/server/lib/opencode/settings-normalization-runtime.js +2 -0
  240. package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
  241. package/server/lib/opencode/settings-registry.json +18 -0
  242. package/server/lib/opencode/settings-runtime.js +36 -9
  243. package/server/lib/opencode/settings-runtime.test.js +158 -2
  244. package/server/lib/opencode/shutdown-runtime.js +3 -0
  245. package/server/lib/opencode/shutdown-runtime.test.js +24 -0
  246. package/server/lib/opencode/theme-archive.js +53 -0
  247. package/server/lib/opencode/theme-catalog.js +171 -0
  248. package/server/lib/opencode/theme-catalog.test.js +125 -0
  249. package/server/lib/opencode/theme-import.test.js +144 -0
  250. package/server/lib/opencode/theme-runtime.js +100 -28
  251. package/server/lib/opencode/theme-runtime.test.js +15 -0
  252. package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
  253. package/server/lib/permission-auto-accept/runtime.js +13 -0
  254. package/server/lib/permission-auto-accept/runtime.test.js +37 -1
  255. package/server/lib/project-context/DOCUMENTATION.md +13 -3
  256. package/server/lib/project-context/runtime.js +8 -2
  257. package/server/lib/project-context/runtime.test.js +54 -0
  258. package/server/lib/projects/DOCUMENTATION.md +2 -1
  259. package/server/lib/projects/project-config.js +40 -11
  260. package/server/lib/projects/project-config.test.js +97 -0
  261. package/server/lib/projects/project-id.js +26 -0
  262. package/server/lib/quota/DOCUMENTATION.md +0 -1
  263. package/server/lib/quota/providers/index.js +0 -7
  264. package/server/lib/realtime-proxy-terminal.test.js +150 -0
  265. package/server/lib/realtime-proxy.js +2 -0
  266. package/server/lib/relay/DOCUMENTATION.md +20 -0
  267. package/server/lib/relay/host-client.js +7 -0
  268. package/server/lib/relay/host-client.test.js +14 -0
  269. package/server/lib/routing/DOCUMENTATION.md +83 -0
  270. package/server/lib/routing/defaults.js +98 -0
  271. package/server/lib/routing/feature-flag.js +11 -0
  272. package/server/lib/routing/history.js +34 -0
  273. package/server/lib/routing/jev.js +98 -0
  274. package/server/lib/routing/routes.http.test.js +100 -0
  275. package/server/lib/routing/routes.js +81 -0
  276. package/server/lib/routing/runtime.js +217 -0
  277. package/server/lib/routing/runtime.test.js +188 -0
  278. package/server/lib/routing/store.js +249 -0
  279. package/server/lib/routing/store.test.js +96 -0
  280. package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
  281. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
  282. package/server/lib/scheduled-tasks/runtime.js +14 -4
  283. package/server/lib/scheduled-tasks/runtime.test.js +67 -0
  284. package/server/lib/security/request-security.js +9 -5
  285. package/server/lib/security/request-security.test.js +37 -0
  286. package/server/lib/session-assist/DOCUMENTATION.md +131 -66
  287. package/server/lib/session-assist/context.js +127 -0
  288. package/server/lib/session-assist/context.test.js +132 -0
  289. package/server/lib/session-assist/prompt.js +54 -0
  290. package/server/lib/session-assist/runtime.js +111 -257
  291. package/server/lib/session-assist/runtime.test.js +203 -0
  292. package/server/lib/small-model/DOCUMENTATION.md +14 -2
  293. package/server/lib/small-model/call.js +37 -6
  294. package/server/lib/small-model/call.test.js +182 -0
  295. package/server/lib/terminal/DOCUMENTATION.md +23 -2
  296. package/server/lib/terminal/runtime.js +27 -5
  297. package/server/lib/terminal/runtime.test.js +67 -0
  298. package/server/lib/terminal/shutdown.js +106 -0
  299. package/server/lib/terminal/shutdown.test.js +84 -0
  300. package/server/lib/tts/service.test.js +78 -0
  301. package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
  302. package/server/lib/ui-auth/session-cookie.js +51 -0
  303. package/server/lib/ui-auth/session-cookie.test.js +46 -0
  304. package/server/lib/ui-auth/ui-auth.js +56 -17
  305. package/server/lib/ui-auth/ui-auth.test.js +96 -0
  306. package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
  307. package/server/lib/walkthrough/pr-source.test.js +21 -0
  308. package/server/lib/walkthrough/pull-request.js +7 -4
  309. package/server/lib/walkthrough/pull-request.test.js +16 -0
  310. package/server/lib/walkthrough/routes.js +19 -0
  311. package/server/lib/walkthrough/routes.test.js +26 -0
  312. package/server/lib/walkthrough/sources.js +13 -2
  313. package/server/opencode-worktree-readiness.test.js +130 -0
  314. package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
  315. package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
  316. package/dist/assets/FilesView-BdTOpew9.js +0 -17
  317. package/dist/assets/GitView-Dqf9vHJh.js +0 -6
  318. package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
  320. package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
  321. package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
  322. package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
  323. package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
  324. package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
  325. package/dist/assets/de-C8jV7J9y.js +0 -10
  326. package/dist/assets/es-BI6J1M5R.js +0 -16
  327. package/dist/assets/fr-D6oDFt7E.js +0 -14
  328. package/dist/assets/index-BqhiCmYS.js +0 -39
  329. package/dist/assets/index-FqSOBz0J.css +0 -1
  330. package/dist/assets/ja-C_4i5EQu.js +0 -16
  331. package/dist/assets/ko-BFbOTpbq.js +0 -16
  332. package/dist/assets/main-C9ETLKFc.js +0 -2
  333. package/dist/assets/main-CJmb2dF8.js +0 -930
  334. package/dist/assets/markdownCore-DVuZpnli.js +0 -5
  335. package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
  336. package/dist/assets/mobile-BUT6-lo1.js +0 -2
  337. package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
  338. package/dist/assets/pl-COFvT56l.js +0 -16
  339. package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
  340. package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
  341. package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
  342. package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
  343. package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
  344. package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
  345. package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
  346. package/dist/assets/surface-BvRzS58U.js +0 -1
  347. package/dist/assets/tr-B8Q0Go2l.js +0 -7
  348. package/dist/assets/uk-Dk4a-Fn-.js +0 -16
  349. package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
  350. package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
  351. package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
  352. package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
  353. package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
  354. package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
  355. package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
  356. package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
  357. package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
  358. package/server/lib/mcp-reconnect/runtime.js +0 -122
  359. package/server/lib/mcp-reconnect/runtime.test.js +0 -140
  360. package/server/lib/quota/providers/crof.js +0 -96
  361. package/server/lib/quota/providers/crof.test.js +0 -98
  362. /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
@@ -213,6 +213,46 @@ describe('ui auth client credential seam', () => {
213
213
  });
214
214
  expect(mountedServeCalled).toBe(true);
215
215
 
216
+ const guestReq = { method: 'GET', path: '/api/guests/hello/panel/index.html', url: `/api/guests/hello/panel/index.html?oc_url_token=${encodeURIComponent(urlToken)}`, headers: {} };
217
+ const guestRes = createResponse();
218
+ let guestCalled = false;
219
+ await auth.requireAuth(guestReq, guestRes, () => {
220
+ guestCalled = true;
221
+ });
222
+ expect(guestCalled).toBe(true);
223
+
224
+ // A guest-scoped token opens only that guest's files. It is the token a
225
+ // sandboxed guest page can read from its own URL, so it must fail on every
226
+ // system path and on another guest's files.
227
+ const guestMintReq = { method: 'POST', path: '/auth/url-token', query: { scope: 'guest:hello' }, headers: { authorization: 'Bearer client-token', accept: 'application/json' } };
228
+ const guestMintRes = createResponse();
229
+ await auth.handleUrlAuthToken(guestMintReq, guestMintRes);
230
+ const guestToken = guestMintRes.body.token;
231
+ expect(typeof guestToken).toBe('string');
232
+ const guestScopedReq = { method: 'GET', path: '/api/guests/hello/panel/main.js', url: `/api/guests/hello/panel/main.js?oc_url_token=${encodeURIComponent(guestToken)}`, headers: {} };
233
+ let guestScopedCalled = false;
234
+ await auth.requireAuth(guestScopedReq, createResponse(), () => {
235
+ guestScopedCalled = true;
236
+ });
237
+ expect(guestScopedCalled).toBe(true);
238
+ for (const forbidden of [
239
+ { method: 'GET', path: '/api/fs/raw', url: `/api/fs/raw?path=%2Ftmp%2Fimage.png&oc_url_token=${encodeURIComponent(guestToken)}` },
240
+ { method: 'GET', path: '/api/event', url: `/api/event?oc_url_token=${encodeURIComponent(guestToken)}` },
241
+ { method: 'GET', path: '/api/guests/other/panel/main.js', url: `/api/guests/other/panel/main.js?oc_url_token=${encodeURIComponent(guestToken)}` },
242
+ { method: 'GET', path: '/api/guests', url: `/api/guests?oc_url_token=${encodeURIComponent(guestToken)}` },
243
+ ]) {
244
+ const forbiddenRes = createResponse();
245
+ let forbiddenCalled = false;
246
+ await auth.requireAuth({ ...forbidden, headers: {} }, forbiddenRes, () => {
247
+ forbiddenCalled = true;
248
+ });
249
+ expect(forbiddenCalled).toBe(false);
250
+ expect(forbiddenRes.statusCode).toBe(401);
251
+ }
252
+ const badScopeRes = createResponse();
253
+ await auth.handleUrlAuthToken({ ...guestMintReq, query: { scope: 'admin' } }, badScopeRes);
254
+ expect(badScopeRes.statusCode).toBe(400);
255
+
216
256
  const dictationWsReq = {
217
257
  method: 'GET',
218
258
  path: '/api/dictation/ws',
@@ -317,3 +357,59 @@ describe('ui auth client credential seam', () => {
317
357
  expect(expiresAt).toBeLessThanOrEqual(Date.now() + 124_000);
318
358
  });
319
359
  });
360
+
361
+ // issue #2377: browsers key cookie jars on host only, so two instances on one
362
+ // LAN IP (different ports) collided on `oc_ui_session`. Cookies are now scoped
363
+ // by the request port so each instance owns its own slot.
364
+ describe('ui auth port-scoped session cookies (issue #2377)', () => {
365
+ const loginHost = async (auth, host) => {
366
+ const req = { method: 'POST', headers: { host }, body: { password: 'secret' } };
367
+ const res = createResponse();
368
+ await auth.handleSessionCreate(req, res);
369
+ return String(res.getHeader('set-cookie') || '').split(';', 1)[0];
370
+ };
371
+
372
+ it('names the issued cookie with the request port', async () => {
373
+ const createUiAuth = await loadCreateUiAuth();
374
+ const auth = createUiAuth({ password: 'secret' });
375
+
376
+ const issued = await loginHost(auth, '192.168.0.1:3000');
377
+ expect(issued).toMatch(/^oc_ui_session_3000=/);
378
+ expect(issued.length).toBeGreaterThan('oc_ui_session_3000='.length);
379
+ });
380
+
381
+ it('keeps the bare cookie name when the host has no explicit port', async () => {
382
+ const createUiAuth = await loadCreateUiAuth();
383
+ const auth = createUiAuth({ password: 'secret' });
384
+
385
+ expect(await loginHost(auth, '192.168.0.1')).toMatch(/^oc_ui_session=/);
386
+ });
387
+
388
+ it('reads the slot for the request port and ignores another port cookie', async () => {
389
+ const createUiAuth = await loadCreateUiAuth();
390
+ const auth = createUiAuth({ password: 'secret' });
391
+
392
+ // Valid token issued against :3000.
393
+ const portCookie = await loginHost(auth, '192.168.0.1:3000');
394
+ const validToken = portCookie.slice('oc_ui_session_3000='.length);
395
+
396
+ // A request that lands on :3001 must read the :3001 slot, NOT the :3000 one,
397
+ // even though the browser ships both cookies to either port.
398
+ const wrongSlot = {
399
+ method: 'GET',
400
+ headers: { host: '192.168.0.1:3001', cookie: `oc_ui_session_3000=${validToken}; oc_ui_session_3001=stale` },
401
+ };
402
+ const wrongRes = createResponse();
403
+ await auth.handleSessionStatus(wrongSlot, wrongRes);
404
+ expect(wrongRes.body.authenticated).toBe(false);
405
+
406
+ // The same token read on its own port authenticates.
407
+ const rightSlot = {
408
+ method: 'GET',
409
+ headers: { host: '192.168.0.1:3000', cookie: `oc_ui_session_3000=${validToken}` },
410
+ };
411
+ const rightRes = createResponse();
412
+ await auth.handleSessionStatus(rightSlot, rightRes);
413
+ expect(rightRes.body.authenticated).toBe(true);
414
+ });
415
+ });
@@ -86,11 +86,21 @@ its current hunk index while visible; regeneration remains user-initiated. The
86
86
  content-addressed cache continues to reuse an old review only when its hunks
87
87
  match, and otherwise reports stale anchors and uncovered current hunks.
88
88
 
89
- The panel offers the current branch's pull request on its own: it registers with
90
- the shared GitHub PR status store (`useGitHubPrStatusStore`) rather than waiting
91
- for the pull request panel to have been visited. That store already dedupes
92
- concurrent requests by signature and throttles by TTL, so several panels asking
93
- the same question produce one call to GitHub.
89
+ PR mode uses the same searchable, paginated selector as Changes. Its list loads
90
+ only while PR mode is visible. Selection ownership and handoff rules are in
91
+ `packages/ui/src/stores/DOCUMENTATION.md`.
92
+
93
+ PR sources may include `sourceRepo: { owner, repo }`. This qualifies both the
94
+ GitHub request and the cache/job key as `pr:<owner>/<repo>:<number>`. Existing
95
+ number-only sources retain `pr:<number>` and resolve the directory's repository.
96
+ The PR panel forwards its resolved repository when opening walkthrough.
97
+
98
+ `GET /api/walkthrough/pr-diff` accepts `directory` and a JSON `source` restricted
99
+ to PRs. It returns GitHub's complete published diff as text, with no model
100
+ readiness checks or generation. Successful empty patches return 200; auth,
101
+ GitHub and malformed-response failures remain errors. Walkthrough generation
102
+ keeps its existing empty-diff refusal. UI comparison behavior is documented in
103
+ `packages/ui/src/components/views/DOCUMENTATION.md`.
94
104
 
95
105
  ## No truncation
96
106
 
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseSource, sourceKey, loadSourceSections } from './sources.js';
3
+
4
+ describe('repository-qualified PR sources', () => {
5
+ it('keeps old cache keys and separates equal PR numbers in different repositories', () => {
6
+ expect(sourceKey(parseSource({ kind: 'pr', number: 42 }))).toBe('pr:42');
7
+ const upstream = parseSource({ kind: 'pr', number: 42, sourceRepo: { owner: 'upstream', repo: 'project' } });
8
+ const fork = parseSource({ kind: 'pr', number: 42, sourceRepo: { owner: 'fork', repo: 'project' } });
9
+ expect(sourceKey(upstream)).not.toBe(sourceKey(fork));
10
+ });
11
+
12
+ it('hands the selected repository to the walkthrough diff loader', async () => {
13
+ const source = parseSource({ kind: 'pr', number: 42, sourceRepo: { owner: 'upstream', repo: 'project' } });
14
+ let received;
15
+ await loadSourceSections('/repo', source, { getPullRequestDiff: async (...args) => {
16
+ received = args;
17
+ return { patch: 'published patch', meta: {} };
18
+ } });
19
+ expect(received).toEqual(['/repo', 42, source.sourceRepo]);
20
+ });
21
+ });
@@ -1,4 +1,5 @@
1
1
  import { getOctokitOrNull } from '../github/octokit.js';
2
+ import assert from 'node:assert/strict';
2
3
  import { resolveGitHubRepoFromDirectory } from '../github/repo/index.js';
3
4
 
4
5
  /**
@@ -8,7 +9,7 @@ import { resolveGitHubRepoFromDirectory } from '../github/repo/index.js';
8
9
  * three-dot semantics used for local branch reviews: work merged in from the
9
10
  * base branch is not part of it.
10
11
  */
11
- export async function getPullRequestDiff(directory, number) {
12
+ export async function getPullRequestDiff(directory, number, sourceRepo, { allowEmpty = false } = {}) {
12
13
  const octokit = getOctokitOrNull();
13
14
  if (!octokit) {
14
15
  throw Object.assign(new Error('Connect a GitHub account to review pull requests'), {
@@ -19,7 +20,8 @@ export async function getPullRequestDiff(directory, number) {
19
20
 
20
21
  // The resolver returns `{ repo, remoteUrl }`, not the repo itself. Reading
21
22
  // `.owner` off the wrapper made this check fail for every repository.
22
- const { repo } = await resolveGitHubRepoFromDirectory(directory);
23
+ const resolved = sourceRepo ? { repo: sourceRepo } : await resolveGitHubRepoFromDirectory(directory);
24
+ const { repo } = resolved;
23
25
  if (!repo?.owner || !repo?.repo) {
24
26
  throw Object.assign(new Error('This directory has no GitHub remote'), {
25
27
  statusCode: 400,
@@ -34,8 +36,9 @@ export async function getPullRequestDiff(directory, number) {
34
36
  headers: { accept: 'application/vnd.github.v3.diff' },
35
37
  });
36
38
 
37
- const patch = typeof response?.data === 'string' ? response.data : '';
38
- if (!patch.trim()) {
39
+ assert.match(response.data, /^(?:diff --git |\s*$)/, 'GitHub returned an invalid pull request diff');
40
+ const patch = response.data;
41
+ if (!allowEmpty && !patch.trim()) {
39
42
  throw Object.assign(new Error(`Pull request #${number} has no diff`), {
40
43
  statusCode: 404,
41
44
  code: 'empty-diff',
@@ -56,6 +56,22 @@ describe('getPullRequestDiff', () => {
56
56
  expect(request).not.toHaveBeenCalled();
57
57
  });
58
58
 
59
+ it('uses the selected upstream repository rather than the local fork', async () => {
60
+ const result = await getPullRequestDiff('/repo', 42, { owner: 'upstream', repo: 'project' });
61
+ expect(result.meta).toEqual({ owner: 'upstream', repo: 'project', number: 42 });
62
+ expect(resolveGitHubRepoFromDirectory).not.toHaveBeenCalled();
63
+ expect(request).toHaveBeenCalledWith('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
64
+ owner: 'upstream', repo: 'project', pull_number: 42, headers: { accept: 'application/vnd.github.v3.diff' },
65
+ });
66
+ });
67
+
68
+ it('allows an empty comparison but rejects malformed GitHub bodies', async () => {
69
+ request.mockResolvedValue({ data: '' });
70
+ expect((await getPullRequestDiff('/repo', 42, undefined, { allowEmpty: true })).patch).toBe('');
71
+ request.mockResolvedValue({ data: { message: 'Not a diff' } });
72
+ await expect(getPullRequestDiff('/repo', 42)).rejects.toThrow();
73
+ });
74
+
59
75
  it('asks the user to connect GitHub before anything else', async () => {
60
76
  getOctokitOrNull.mockReturnValue(null);
61
77
 
@@ -1,3 +1,5 @@
1
+ import { parseSource } from './sources.js';
2
+
1
3
  // `req.destroyed` is true for every healthy request once the body parser has
2
4
  // consumed the stream, so using it as a disconnect check silently swallows every
3
5
  // response. The response socket is the one that actually reflects whether the
@@ -51,6 +53,23 @@ export function registerWalkthroughRoutes(app, { getWalkthroughService }) {
51
53
  }
52
54
  });
53
55
 
56
+ // The comparison view needs the complete published patch, without model
57
+ // readiness checks, generated-file filtering, or local working-tree reads.
58
+ app.get('/api/walkthrough/pr-diff', async (req, res) => {
59
+ try {
60
+ const query = new URL(req.originalUrl, 'http://localhost').searchParams;
61
+ const directory = query.get('directory')?.trim() ?? '';
62
+ if (!directory) return res.status(400).json({ error: 'directory parameter is required' });
63
+ const source = parseSource(readSource(query.get('source')));
64
+ if (source.kind !== 'pr') return res.status(400).json({ error: 'A pull request source is required' });
65
+ const { getPullRequestDiff } = await getWalkthroughService();
66
+ const { patch } = await getPullRequestDiff(directory, source.number, source.sourceRepo, { allowEmpty: true });
67
+ res.type('text/plain').send(patch);
68
+ } catch (error) {
69
+ respondWithError(res, error, 'Failed to load pull request diff');
70
+ }
71
+ });
72
+
54
73
  // Deliberately not aborted when the client disconnects: generation runs for
55
74
  // minutes and a refresh must not throw the work away. Leaving detaches the
56
75
  // client; the job finishes and caches its result. Stopping is an explicit
@@ -18,6 +18,11 @@ describe('walkthrough routes', () => {
18
18
  let generateCalls = 0;
19
19
 
20
20
  const service = {
21
+ async getPullRequestDiff(directory, number, sourceRepo, options) {
22
+ lastArgs = { directory, number, sourceRepo, options };
23
+ if (number === 99) throw Object.assign(new Error('GitHub unavailable'), { statusCode: 503 });
24
+ return { patch: number === 1 ? '' : 'diff --git a/a.ts b/a.ts\n' };
25
+ },
21
26
  async getWalkthrough(args) {
22
27
  lastArgs = args;
23
28
  return { walkthrough: null, hunks: [], hunkCount: 0, generating: Boolean(job) };
@@ -84,6 +89,27 @@ describe('walkthrough routes', () => {
84
89
  expect(body.walkthrough).toEqual({ title: 'DONE' });
85
90
  });
86
91
 
92
+ it('serves the published PR snapshot with its repository, without generating', async () => {
93
+ const source = { kind: 'pr', number: 42, sourceRepo: { owner: 'upstream', repo: 'project' } };
94
+ const before = generateCalls;
95
+ const response = await fetch(`${base}/api/walkthrough/pr-diff?directory=/repo&source=${encodeURIComponent(JSON.stringify(source))}`);
96
+ expect(response.headers.get('content-type')).toContain('text/plain');
97
+ expect(await response.text()).toBe('diff --git a/a.ts b/a.ts\n');
98
+ expect(lastArgs).toEqual({ directory: '/repo', number: 42, sourceRepo: source.sourceRepo, options: { allowEmpty: true } });
99
+ expect(generateCalls).toBe(before);
100
+ });
101
+
102
+ it('distinguishes empty PRs, upstream failure, and invalid sources', async () => {
103
+ const request = (source) => fetch(`${base}/api/walkthrough/pr-diff?directory=/repo&source=${encodeURIComponent(JSON.stringify(source))}`);
104
+ const empty = await request({ kind: 'pr', number: 1 });
105
+ expect(empty.status).toBe(200);
106
+ expect(await empty.text()).toBe('');
107
+ expect((await request({ kind: 'pr', number: 99 })).status).toBe(503);
108
+ for (const source of [{ kind: 'pr', number: -1 }, { kind: 'branch', baseRef: 'main', headRef: 'feature' }, { kind: 'pr', number: 1, sourceRepo: { owner: '../bad', repo: 'repo' } }]) {
109
+ expect((await request(source)).status).toBe(400);
110
+ }
111
+ });
112
+
87
113
  it('delivers the result to a client that reconnected after a refresh', async () => {
88
114
  const controller = new AbortController();
89
115
  const seen = generateCalls;
@@ -1,4 +1,5 @@
1
1
  import { getDiff, getRangeDiff, getCommitDiff, getUntrackedDiffs, listUntrackedPaths } from '../git/service.js';
2
+ import assert from 'node:assert/strict';
2
3
 
3
4
  // A walkthrough source resolves to one or more diff *sections*. A section is a
4
5
  // patch plus the scope its hunk ids live in; keeping staged and working-tree
@@ -45,6 +46,16 @@ export function parseSource(raw) {
45
46
  if (!Number.isInteger(number) || number <= 0) {
46
47
  throw new WalkthroughSourceError('pr sources require a positive number');
47
48
  }
49
+ if (raw.sourceRepo !== undefined) {
50
+ const { owner, repo } = raw.sourceRepo ?? {};
51
+ try {
52
+ assert.match(owner, /^[a-zA-Z0-9-]+$/);
53
+ assert.match(repo, /^[a-zA-Z0-9_.-]+$/);
54
+ } catch {
55
+ throw new WalkthroughSourceError('pr sources require a valid repository');
56
+ }
57
+ return { kind: 'pr', number, sourceRepo: { owner, repo } };
58
+ }
48
59
  return { kind: 'pr', number };
49
60
  }
50
61
 
@@ -71,7 +82,7 @@ export function sourceKey(source) {
71
82
  if (source.kind === 'working-tree') return `working-tree:${source.scope}`;
72
83
  if (source.kind === 'branch') return `branch:${source.baseRef}...${source.headRef}`;
73
84
  if (source.kind === 'commit') return `commit:${source.hash}`;
74
- return `pr:${source.number}`;
85
+ return source.sourceRepo ? `pr:${source.sourceRepo.owner}/${source.sourceRepo.repo}:${source.number}` : `pr:${source.number}`;
75
86
  }
76
87
 
77
88
  // `git diff` never reports untracked files, so a brand-new file would be
@@ -129,7 +140,7 @@ export async function loadSourceSections(directory, source, { getPullRequestDiff
129
140
  throw new WalkthroughSourceError('Pull request diffs are unavailable', 500);
130
141
  }
131
142
 
132
- const { patch, meta } = await getPullRequestDiff(directory, source.number);
143
+ const { patch, meta } = await getPullRequestDiff(directory, source.number, source.sourceRepo);
133
144
  return {
134
145
  sections: patch && patch.trim() ? [{ scope: `pr:${source.number}`, patch }] : [],
135
146
  meta: meta || {},
@@ -0,0 +1,130 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import express from 'express';
3
+ import path from 'node:path';
4
+ import { registerOpenCodeProxy } from './lib/opencode/proxy.js';
5
+
6
+ const servers = [];
7
+ const listen = async (app) => {
8
+ const server = await new Promise((resolve) => {
9
+ const listening = app.listen(0, '127.0.0.1', () => resolve(listening));
10
+ });
11
+ servers.push(server);
12
+ return `http://127.0.0.1:${server.address().port}`;
13
+ };
14
+
15
+ afterEach(async () => {
16
+ await Promise.all(servers.splice(0).map((server) => new Promise((resolve) => {
17
+ server.closeAllConnections();
18
+ server.close(resolve);
19
+ })));
20
+ });
21
+
22
+ const worktree = '/repo/worktree with spaces';
23
+ const projectConfig = { mcp: { projectOnly: { type: 'local', command: ['test-mcp'], enabled: false } } };
24
+
25
+ async function createFixture({ timeoutMs = 1000 } = {}) {
26
+ let bootstrap = { status: 'pending', phase: 'directory-created', error: null };
27
+ let instance;
28
+ const forwarded = [];
29
+ const probed = [];
30
+ const upstream = express();
31
+ upstream.use(express.json());
32
+ upstream.use((req, res) => {
33
+ forwarded.push(req.path);
34
+ // Model OpenCode's instance cache: even a GET can permanently capture the
35
+ // empty directory, and a later session inherits that project's identity.
36
+ instance ??= bootstrap.phase === 'directory-created'
37
+ ? { projectID: 'global', config: {} }
38
+ : { projectID: 'project-3464', config: projectConfig };
39
+ if (req.path === '/config') return res.json(instance.config);
40
+ if (req.path === '/session' && req.method === 'POST') {
41
+ return res.json({ projectID: instance.projectID, directory: req.query.directory, title: req.body.title });
42
+ }
43
+ res.json([]);
44
+ });
45
+ const upstreamUrl = await listen(upstream);
46
+ const app = express();
47
+ app.use(express.json());
48
+ registerOpenCodeProxy(app, {
49
+ fs: {}, os: {}, path,
50
+ OPEN_CODE_READY_GRACE_MS: 0,
51
+ WORKTREE_READY_TIMEOUT_MS: timeoutMs,
52
+ getRuntime: () => ({ openCodePort: new URL(upstreamUrl).port, isOpenCodeReady: true }),
53
+ getOpenCodeAuthHeaders: () => ({}),
54
+ buildOpenCodeUrl: (requestPath) => `${upstreamUrl}${requestPath}`,
55
+ ensureOpenCodeApiPrefix: () => {},
56
+ readWorktreeBootstrapStatus: async (directory) => {
57
+ probed.push(directory);
58
+ return directory === worktree ? bootstrap : { status: 'ready', phase: 'setup-ready' };
59
+ },
60
+ });
61
+ return {
62
+ url: await listen(app), forwarded, probed,
63
+ setBootstrap: (next) => { bootstrap = next; },
64
+ };
65
+ }
66
+
67
+ describe('issue #3464 worktree OpenCode initialization', () => {
68
+ it('holds config and session creation until Git is ready, while setup remains pending', async () => {
69
+ const fixture = await createFixture();
70
+ const config = fetch(`${fixture.url}/api/config?directory=${encodeURIComponent(worktree)}`);
71
+ const session = fetch(`${fixture.url}/api/session?directory=${encodeURIComponent(worktree)}`, {
72
+ method: 'POST', headers: { 'content-type': 'application/json' },
73
+ body: JSON.stringify({ title: 'First worktree session' }),
74
+ });
75
+ await expect.poll(() => fixture.probed.length).toBeGreaterThanOrEqual(2);
76
+ expect(fixture.forwarded).toEqual([]);
77
+ fixture.setBootstrap({ status: 'pending', phase: 'git-ready', error: null });
78
+ expect(await (await config).json()).toEqual(projectConfig);
79
+ expect(await (await session).json()).toEqual({
80
+ projectID: 'project-3464', directory: worktree, title: 'First worktree session',
81
+ });
82
+ });
83
+
84
+ it('gates encoded directory headers and the separately forwarded session list', async () => {
85
+ const fixture = await createFixture();
86
+ const response = fetch(`${fixture.url}/api/session`, { headers: {
87
+ 'x-opencode-directory': encodeURIComponent(worktree), 'x-opencode-directory-encoding': 'uri',
88
+ } });
89
+ await expect.poll(() => fixture.probed.length).toBeGreaterThan(0);
90
+ expect(fixture.probed[0]).toBe(worktree);
91
+ expect(fixture.forwarded).toEqual([]);
92
+ fixture.setBootstrap({ status: 'ready', phase: 'setup-ready', error: null });
93
+ expect((await response).status).toBe(200);
94
+ expect(fixture.forwarded).toEqual(['/session']);
95
+ });
96
+
97
+ it('does not block unrelated directories while a worktree is pending', async () => {
98
+ const fixture = await createFixture();
99
+ expect((await fetch(`${fixture.url}/api/session?directory=/other-project`)).status).toBe(200);
100
+ expect(fixture.probed).toEqual(['/other-project']);
101
+ });
102
+
103
+ it('does not forward an abandoned request when checkout later finishes', async () => {
104
+ const fixture = await createFixture();
105
+ const controller = new AbortController();
106
+ const response = fetch(`${fixture.url}/api/config?directory=${encodeURIComponent(worktree)}`, {
107
+ signal: controller.signal,
108
+ });
109
+ const rejected = expect(response).rejects.toThrow();
110
+ await expect.poll(() => fixture.probed.length).toBeGreaterThan(0);
111
+ controller.abort();
112
+ await rejected;
113
+ // Allow the server to observe the closed client before releasing checkout.
114
+ await new Promise((resolve) => setTimeout(resolve, 100));
115
+ fixture.setBootstrap({ status: 'ready', phase: 'setup-ready', error: null });
116
+ await new Promise((resolve) => setTimeout(resolve, 100));
117
+ expect(fixture.forwarded).toEqual([]);
118
+ });
119
+
120
+ it('fails closed on bootstrap failure or timeout without initializing OpenCode', async () => {
121
+ const fixture = await createFixture({ timeoutMs: 0 });
122
+ const timedOut = await fetch(`${fixture.url}/api/config?directory=${encodeURIComponent(worktree)}`);
123
+ expect(timedOut.status).toBe(503);
124
+ fixture.setBootstrap({ status: 'failed', phase: 'directory-created', error: 'checkout failed' });
125
+ const failed = await fetch(`${fixture.url}/api/session?directory=${encodeURIComponent(worktree)}`, { method: 'POST' });
126
+ expect(failed.status).toBe(503);
127
+ expect(await failed.json()).toEqual({ error: 'checkout failed' });
128
+ expect(fixture.forwarded).toEqual([]);
129
+ });
130
+ });
@@ -1,4 +0,0 @@
1
- import{R as i,f as $e,j as n}from"./vendor-react-BUCujlKS.js";import{u as le,K as Ae,a as Ne}from"./index-BqhiCmYS.js";import{b as bn,u as vn,a as jn,B as Sn,C as Nn,h as kn,e as Cn,g as Rn,i as Fn,f as Dn}from"./patchFileDiff-8j1apICU.js";import{c as An,b as Vn,i as En,u as Ln,a as Tn,d as Mn}from"./useGitComparison-Dfxaqd0s.js";import{f as Te,B as ke,I as M,h as xe,W as It,ac as Bn,ad as In,bb as Pn,ag as On,ae as zn,ah as _n,ai as ce,y as Rt,k as Hn,cK as Pt,t as Ke,i1 as Kn,av as Ge,a7 as Gn,a8 as $n,a9 as Wn,hS as Ot,i2 as qn,aw as zt,fP as Un,at as nt,au as Jn,bR as _t,ap as Xn,ar as Yn,as as Qn,ao as Zn}from"./useAppFontEffects-DpFNefUA.js";import{c as es,b as ts}from"./runtimeEndpointReset-B8LYpWvr.js";import{P as ns}from"./PierreDiffViewer-DCsydlKs.js";import{u as ss,v as rs,g as is,W as os}from"./main-CJmb2dF8.js";import"./vendor-scheduler-7OC5HNn7.js";import"./vendor-vite-runtime-DD1OZmLC.js";import"./vendor-zustand-BAGL70zf.js";import"./vendor-zod-BPAANqvH.js";import"./vendor-opencode-sdk-BJ73u-bH.js";import"./VirtualizedFileDiff-DcdgkH-x.js";import"./web-components-CdGfR2XJ.js";import"./computeVirtualFileMetrics-C8byMnRh.js";import"./vendor-shiki-Bbf2GEcE.js";import"./vendor-shikijs-core-CGM_jqPK.js";import"./vendor-shikijs-types-CPSbSeK6.js";import"./vendor-shikijs-vscode-textmate-BgLrS_2D.js";import"./vendor-hast-util-to-html-BKBhbTDb.js";import"./vendor-html-void-elements-CkdplsNl.js";import"./vendor-property-information-rJCbAzvX.js";import"./vendor-zwitch-C2o2j-tx.js";import"./vendor-stringify-entities-CsH1VEn9.js";import"./vendor-character-entities-legacy-BpoLgcEt.js";import"./vendor-character-entities-html4-D7taRCQ7.js";import"./vendor-ccount-c2V3InAJ.js";import"./vendor-comma-separated-tokens-D8TqSuIK.js";import"./vendor-space-separated-tokens-DD3iYX1K.js";import"./vendor-hast-util-whitespace-D4Wp6AEg.js";import"./vendor-shikijs-engine-oniguruma-DPWuIF1G.js";import"./themeResolver-BmJyTmjJ.js";import"./vendor-pierre-theming-C6Z4M5_u.js";import"./vendor-shikijs-engine-javascript-Cme4830A.js";import"./vendor-oniguruma-to-es-DLBnFDCE.js";import"./vendor-oniguruma-parser-C0D4_9rT.js";import"./vendor-regex-BBWcyTkg.js";import"./vendor-regex-utilities-2BQyy0xI.js";import"./vendor-regex-recursion-JU4mF-dD.js";import"./vendor-shikijs-transformers-CZWsLLKm.js";import"./isDiffPlainText-45ujMPGb.js";import"./vendor-diff-C3JhlQdm.js";import"./vendor-simplewebauthn-browser-VDA66rT_.js";import"./vendor-base-ui-DdV8vhHn.js";import"./vendor-floating-ui-utils-D6FqdtJt.js";import"./vendor-floating-ui-react-dom-DxYCWdHj.js";import"./vendor-floating-ui-dom-BTvzWks8.js";import"./vendor-floating-ui-core-PuGzNPnN.js";import"./vendor-use-sync-external-store-D6wYuSt-.js";import"./vendor-class-variance-authority-5VPnzWs2.js";import"./vendor-clsx-B-dksMZM.js";import"./vendor-dnd-kit-core-BZknOX2w.js";import"./vendor-dnd-kit-utilities-B_MdXhJC.js";import"./vendor-dnd-kit-accessibility-BHxJPORy.js";import"./vendor-dnd-kit-sortable-CsEagZ81.js";import"./vendor-framer-motion-livSKyyV.js";import"./vendor-motion-dom-D_o3BTI3.js";import"./vendor-motion-utils-BGh8QBgx.js";import"./vendor-tanstack-react-virtual-g05Cx3z9.js";import"./vendor-tanstack-virtual-core-Dubg10g5.js";import"./contextPanelEmbeddedChat-CirXEPJq.js";import"./vendor-sonner-DFyL3AvE.js";import"./vendor-cmdk-DgLInKvT.js";import"./vendor-radix-ui-react-dialog-B94Bt2qq.js";import"./vendor-radix-ui-primitive-Dc_FVRD7.js";import"./vendor-radix-ui-react-compose-refs-DcAh5WdT.js";import"./vendor-radix-ui-react-context-Czxm7Z62.js";import"./vendor-radix-ui-react-id-f5BLKEVw.js";import"./vendor-radix-ui-react-use-layout-effect-D_3IhJdJ.js";import"./vendor-radix-ui-react-use-controllable-state-BFT5YmkA.js";import"./vendor-radix-ui-react-dismissable-layer-D7kXcwgJ.js";import"./vendor-radix-ui-react-primitive-DhVoX9SD.js";import"./vendor-radix-ui-react-slot-BQgQOasS.js";import"./vendor-radix-ui-react-use-callback-ref-BZPnuXAC.js";import"./vendor-radix-ui-react-use-escape-keydown-oph88g-F.js";import"./vendor-radix-ui-react-focus-scope-CiaxW4dD.js";import"./vendor-radix-ui-react-portal-BMJYHWck.js";import"./vendor-radix-ui-react-presence-CNxf-uq4.js";import"./vendor-radix-ui-react-focus-guards-B6qJXYua.js";import"./vendor-react-remove-scroll-DO7dCTnK.js";import"./vendor-tslib-De9GV7Vy.js";import"./vendor-react-remove-scroll-bar-BcFYyj3E.js";import"./vendor-react-style-singleton-q6EMHnwf.js";import"./vendor-get-nonce-C-Z93AgS.js";import"./vendor-use-sidecar-zDC0RhA3.js";import"./vendor-use-callback-ref-D3RC8gqK.js";import"./vendor-aria-hidden-DvXkyWUv.js";import"./vendor-codemirror-language-BgPj_McA.js";import"./vendor-lezer-common-B3-cUCR_.js";import"./vendor-codemirror-state-C9awkmn-.js";import"./vendor-marijn-find-cluster-break-DXwl3gUT.js";import"./vendor-codemirror-view-CiLROUVV.js";import"./vendor-style-mod-NZ5MEPvj.js";import"./vendor-w3c-keyname-Vcq4gwWv.js";import"./vendor-crelt-C8TCjufn.js";import"./vendor-lezer-highlight-CDMHUZ74.js";import"./vendor-codemirror-lang-javascript-Cc2ZYbgX.js";import"./vendor-lezer-javascript-D7Tj24ot.js";import"./vendor-lezer-lr-bhwg1HMg.js";import"./vendor-codemirror-autocomplete-DzPz9Wgq.js";import"./vendor-codemirror-lang-json-CMBS2tG9.js";import"./vendor-lezer-json-Iaex0UN-.js";import"./vendor-codemirror-lang-css-O0cZX78y.js";import"./vendor-lezer-css-C5xB6jxd.js";import"./vendor-codemirror-lang-html-CfgK872x.js";import"./vendor-lezer-html-C0I1p52t.js";import"./vendor-codemirror-lang-markdown-BPGOH9ye.js";import"./vendor-lezer-markdown-C6g-d6P3.js";import"./vendor-codemirror-language-data-DzlSIu_W.js";import"./vendor-codemirror-lang-python-HahnQaAS.js";import"./vendor-lezer-python-CvuFxeZl.js";import"./vendor-codemirror-commands-B7H_j0iU.js";import"./vendor-legendapp-list-BuBPBC3R.js";import"./vendor-tailwind-merge-CPcsbTWB.js";import"./vendor-fuse.js-Dw8P0qyA.js";import"./Virtualizer-D_a8gKmg.js";import"./useInlineCommentController-CscsE4t-.js";import"./appThemeRegistry-BK0dHKa6.js";import"./registerCustomTheme-BBXUCRnG.js";import"./useRootScrollLock-BaCvuRlT.js";import"./scheduledTasksApi-GC4epIYA.js";import"./vendor-cron-parser-CUcP7OfX.js";import"./vendor-luxon-BZ0puJYg.js";const as=i.memo(function(e){const s=$e.c(13),{index:l,staged:p,busyHunk:f,disabled:y,onAction:S}=e,{t:u}=Te();let g;s[0]!==p?(g=p?["unstage"]:["discard","stage"],s[0]=p,s[1]=g):g=s[1];const N=g;let d;s[2]===Symbol.for("react.memo_cache_sentinel")?(d={top:"var(--oc-hunk-action-offset, 0.25rem)"},s[2]=d):d=s[2];let D;s[3]!==N||s[4]!==f||s[5]!==y||s[6]!==l||s[7]!==S||s[8]!==u?(D=n.jsx("div",{className:"pointer-events-auto flex items-center gap-0.5 rounded-full border border-[var(--interactive-border)]/45 bg-[var(--surface-background)]/95 px-1 py-0.5 shadow-sm backdrop-blur-md",children:N.map(k=>{const m=u(k==="stage"?"diffView.hunk.stageTitle":k==="unstage"?"diffView.hunk.unstageTitle":"diffView.hunk.discardTitle",{index:l+1}),A=f?.index===l&&f.action===k;return n.jsx(ke,{variant:"ghost",size:"xs",className:xe("rounded-full bg-transparent text-muted-foreground hover:bg-transparent hover:text-foreground",k==="discard"&&"text-[var(--status-error)] hover:text-[var(--status-error)]",k==="stage"&&"text-[var(--status-success)] hover:text-[var(--status-success)]"),disabled:y||f!==null,title:m,"aria-label":m,onPointerDown:ls,onClick:ne=>{ne.stopPropagation(),S(l,k)},children:n.jsx(M,{name:A?"loader-4":k==="stage"?"add":"arrow-go-back",className:xe(k==="stage"?"size-4":"size-3.5",A&&"animate-spin")})},k)})}),s[3]=N,s[4]=f,s[5]=y,s[6]=l,s[7]=S,s[8]=u,s[9]=D):D=s[9];let w;return s[10]!==l||s[11]!==D?(w=n.jsx("div",{className:"pointer-events-none absolute right-3 z-20",style:d,"data-hunk-actions":l,children:D}),s[10]=l,s[11]=D,s[12]=w):w=s[12],w});function ls(a){return a.stopPropagation()}const Ft=(a,e)=>{if(e.length===0)return null;let s=null,l=null;for(const f of e)f.top<=a?(!s||f.top>s.top)&&(s=f):(!l||f.top<l.top)&&(l=f);const p=s??l;return p?{path:p.path,topOffset:p.top-a}:null},cs=(a,e,s,l)=>Math.min(Math.max(0,l),Math.max(0,a+s-e)),Dt=a=>{if(!a)return null;const e=a.split(`
2
- `);let s=null;for(const l of e){const p=l.match(/^@@\s*-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s*@@/);if(p){const f=Number.parseInt(p[1]??"",10);s=Number.isFinite(f)&&f>=1?f:null;continue}if(s!==null){if(l.startsWith(" ")){s+=1;continue}if(l.startsWith("+"))return s;if(l.startsWith("-"))return Math.max(1,s)}}return null},us=1100,ds=15e3,At=500,Vt=300,Ht=1e6,ft=3,fs=a=>a<=6?a:a<=12?6:a<=25?4:2,Et={status:"loading"},ms=3,ps=i.memo(()=>{const a=$e.c(4),{t:e}=Te();let s;a[0]!==e?(s=e("diffView.binary.unavailable"),a[0]=e,a[1]=s):s=a[1];let l;return a[2]!==s?(l=n.jsx("div",{className:"rounded-lg border border-border/60 bg-background px-3 py-2",children:n.jsx("div",{className:"typography-meta text-muted-foreground",children:s})}),a[2]=s,a[3]=l):l=a[3],l}),Kt={"?":{code:"?",color:"var(--status-info)",descriptionKey:"diffView.change.untracked"},A:{code:"A",color:"var(--status-success)",descriptionKey:"diffView.change.new"},D:{code:"D",color:"var(--status-error)",descriptionKey:"diffView.change.deleted"},R:{code:"R",color:"var(--status-info)",descriptionKey:"diffView.change.renamed"},C:{code:"C",color:"var(--status-info)",descriptionKey:"diffView.change.copied"},M:{code:"M",color:"var(--status-warning)",descriptionKey:"diffView.change.modified"}},hs=Kt.M,gs=a=>{const e=a.index?.trim(),s=a.working_dir?.trim();return e&&e!=="?"?e.charAt(0):s?s.charAt(0):e?.charAt(0)||s?.charAt(0)||"M"},Gt=a=>{const e=gs(a);return Kt[e]??hs},xs=a=>{const{index:e,working_dir:s}=a;return e==="A"||s==="A"||e==="?"||s==="?"},Lt=a=>{const e=a.index?.trim();return!!(e&&e!=="?")},Tt=a=>!!a.working_dir?.trim()||a.index==="?",ys=(a,e)=>Un(a,e),Mt=a=>(a||"").replace(/\\/g,"/").replace(/\/+$/,""),Bt=(a,e)=>{const s=a.split(`
3
- `),l=e.split(`
4
- `),p=Math.min(s.length,l.length);for(let f=0;f<p;f+=1)if(s[f]!==l[f])return f+1;return l.length>s.length?s.length+1:s.length>l.length?Math.max(1,l.length):1},ws=a=>Array.isArray(a)?a.filter(e=>!e||typeof e!="object"?!1:typeof e.file=="string"):[],bs=a=>a==="added"?"A":a==="deleted"?"D":"M",mt=(a,e,s)=>Fn(e)?{original:"",modified:"",isBinary:!0,patch:e,contextMode:s}:{original:"",modified:"",patch:e,fileDiff:Dn(a,e),contextMode:s},$t=(a,e,s)=>{const l=a??0,p=e??0;return!l&&!p?null:n.jsxs("span",{className:xe("typography-meta flex items-center gap-1 text-xs whitespace-nowrap","flex-shrink-0",s?.className),children:[l?n.jsxs("span",{style:{color:"var(--status-success)"},children:["+",l]}):null,p?n.jsxs("span",{style:{color:"var(--status-error)"},children:["-",p]}):null]})},vs=i.memo(a=>{const e=$e.c(63),{scope:s,workingCount:l,stagedCount:p,turnCount:f,branchCount:y,commitCount:S,showCommitOption:u,showBranchOption:g,onScopeChange:N}=a,{t:d}=Te(),[D,w]=i.useState(!1),k=s==="staged"?p:s==="turn"?f:s==="branch"?y??0:s==="commit"?S??0:l;let m;e[0]!==s||e[1]!==d?(m=d(s==="staged"?"diffView.scope.staged":s==="turn"?"diffView.scope.lastTurn":s==="branch"?"diffView.scope.branch":s==="commit"?"commitComparison.mode":"diffView.scope.changed"),e[0]=s,e[1]=d,e[2]=m):m=e[2];const A=m;let ne;e[3]!==d?(ne=d("diffView.scope.selectorAria"),e[3]=d,e[4]=ne):ne=e[4];let X;e[5]!==k?(X=n.jsxs("span",{className:"diff-toolbar__scope-count",children:[": ",k]}),e[5]=k,e[6]=X):X=e[6];let ue;e[7]!==A||e[8]!==X?(ue=n.jsxs("span",{className:"whitespace-nowrap",children:[A,X]}),e[7]=A,e[8]=X,e[9]=ue):ue=e[9];let F;e[10]===Symbol.for("react.memo_cache_sentinel")?(F=n.jsx(M,{name:"arrow-down-s",className:"size-4 flex-shrink-0 opacity-60"}),e[10]=F):F=e[10];let b;e[11]!==ne||e[12]!==ue?(b=n.jsx(Xn,{asChild:!0,children:n.jsxs("button",{type:"button",className:"flex h-7 flex-shrink-0 items-center gap-1.5 rounded-md px-2 typography-ui-label font-semibold text-foreground outline-none hover:bg-interactive-hover focus-visible:ring-2 focus-visible:ring-ring","aria-label":ne,children:[ue,F]})}),e[11]=ne,e[12]=ue,e[13]=b):b=e[13];let z;e[14]!==N?(z=q=>{(q==="working"||q==="staged"||q==="turn"||q==="branch"||q==="commit")&&(N?.(q),w(!1))},e[14]=N,e[15]=z):z=e[15];let se;e[16]!==d?(se=d("diffView.scope.changed"),e[16]=d,e[17]=se):se=e[17];let re;e[18]!==se?(re=n.jsx("span",{children:se}),e[18]=se,e[19]=re):re=e[19];let pe;e[20]!==l?(pe=n.jsx("span",{className:"typography-meta text-muted-foreground",children:l}),e[20]=l,e[21]=pe):pe=e[21];let Y;e[22]!==pe||e[23]!==re?(Y=n.jsx(nt,{value:"working",children:n.jsxs("span",{className:"flex min-w-0 flex-1 items-center justify-between gap-3",children:[re,pe]})}),e[22]=pe,e[23]=re,e[24]=Y):Y=e[24];let de;e[25]!==d?(de=d("diffView.scope.staged"),e[25]=d,e[26]=de):de=e[26];let Q;e[27]!==de?(Q=n.jsx("span",{children:de}),e[27]=de,e[28]=Q):Q=e[28];let $;e[29]!==p?($=n.jsx("span",{className:"typography-meta text-muted-foreground",children:p}),e[29]=p,e[30]=$):$=e[30];let I;e[31]!==Q||e[32]!==$?(I=n.jsx(nt,{value:"staged",children:n.jsxs("span",{className:"flex min-w-0 flex-1 items-center justify-between gap-3",children:[Q,$]})}),e[31]=Q,e[32]=$,e[33]=I):I=e[33];let P;e[34]!==d?(P=d("diffView.scope.lastTurn"),e[34]=d,e[35]=P):P=e[35];let T;e[36]!==P?(T=n.jsx("span",{children:P}),e[36]=P,e[37]=T):T=e[37];let B;e[38]!==f?(B=n.jsx("span",{className:"typography-meta text-muted-foreground",children:f}),e[38]=f,e[39]=B):B=e[39];let Z;e[40]!==T||e[41]!==B?(Z=n.jsx(nt,{value:"turn",children:n.jsxs("span",{className:"flex min-w-0 flex-1 items-center justify-between gap-3",children:[T,B]})}),e[40]=T,e[41]=B,e[42]=Z):Z=e[42];let ee;e[43]!==y||e[44]!==g||e[45]!==d?(ee=g?n.jsx(nt,{value:"branch",children:n.jsxs("span",{className:"flex min-w-0 flex-1 items-center justify-between gap-3",children:[n.jsx("span",{children:d("diffView.scope.branch")}),n.jsx("span",{className:"typography-meta text-muted-foreground",children:y??"…"})]})}):null,e[43]=y,e[44]=g,e[45]=d,e[46]=ee):ee=e[46];let V;e[47]!==S||e[48]!==u||e[49]!==d?(V=u&&n.jsx(nt,{value:"commit",children:n.jsxs("span",{className:"flex min-w-0 flex-1 items-center justify-between gap-3",children:[n.jsx("span",{children:d("commitComparison.mode")}),n.jsx("span",{className:"typography-meta text-muted-foreground",children:S??"…"})]})}),e[47]=S,e[48]=u,e[49]=d,e[50]=V):V=e[50];let W;e[51]!==s||e[52]!==Y||e[53]!==I||e[54]!==Z||e[55]!==ee||e[56]!==V||e[57]!==z?(W=n.jsx(Yn,{align:"start",className:"w-40",children:n.jsxs(Qn,{value:s,onValueChange:z,children:[Y,I,Z,ee,V]})}),e[51]=s,e[52]=Y,e[53]=I,e[54]=Z,e[55]=ee,e[56]=V,e[57]=z,e[58]=W):W=e[58];let he;return e[59]!==D||e[60]!==W||e[61]!==b?(he=n.jsxs(Zn,{open:D,onOpenChange:w,children:[b,W]}),e[59]=D,e[60]=W,e[61]=b,e[62]=he):he=e[62],he}),js=i.memo(a=>{const e=$e.c(11),{changedFiles:s,selectedFile:l,onSelectFile:p}=a,{t:f}=Te();if(s.length===0)return null;let y;if(e[0]!==s||e[1]!==p||e[2]!==l||e[3]!==f){let u;e[5]!==p||e[6]!==l||e[7]!==f?(u=g=>{const N=Gt(g),d=l===g.path;return n.jsx("li",{children:n.jsxs("button",{type:"button",onClick:()=>p(g.path),className:xe("flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors",d?"bg-interactive-selection text-interactive-selection-foreground":"text-muted-foreground hover:bg-interactive-hover hover:text-foreground"),children:[n.jsx(zt,{filePath:g.path,className:"h-3.5 w-3.5 flex-shrink-0"}),n.jsx("span",{className:"typography-micro font-semibold w-4 text-center uppercase",style:{color:N.color},title:f(N.descriptionKey),"aria-label":f(N.descriptionKey),children:N.code}),n.jsx("span",{className:"min-w-0 flex-1 truncate typography-meta",style:{direction:"rtl",textAlign:"left",unicodeBidi:"plaintext"},title:g.path,children:g.path}),$t(g.insertions,g.deletions)]})},g.path)},e[5]=p,e[6]=l,e[7]=f,e[8]=u):u=e[8],y=s.map(u),e[0]=s,e[1]=p,e[2]=l,e[3]=f,e[4]=y}else y=e[4];let S;return e[9]!==y?(S=n.jsx(_t,{outerClassName:"flex-1 min-h-0",className:"px-2 py-2",children:n.jsx("ul",{className:"flex flex-col gap-1",children:y})}),e[9]=y,e[10]=S):S=e[10],S}),Ss=i.memo(a=>{const e=$e.c(20),{filePath:s,diff:l,renderSideBySide:p}=a,{t:f}=Te(),y=l.original.length>0,S=l.modified.length>0,u=p?"flex flex-row gap-6 items-start justify-center":"flex flex-col gap-4 items-center",g=p?"flex flex-col items-center gap-2 flex-1 min-w-0":"flex flex-col items-center gap-2";let N;e[0]===Symbol.for("react.memo_cache_sentinel")?(N={contain:"layout"},e[0]=N):N=e[0];let d;e[1]!==l.original||e[2]!==s||e[3]!==y||e[4]!==g||e[5]!==p||e[6]!==f?(d=y&&n.jsxs("div",{className:g,children:[n.jsx("span",{className:"typography-meta text-muted-foreground font-medium",children:f("diffView.image.original")}),n.jsx("img",{src:l.original,alt:f("diffView.image.originalAlt",{path:s}),className:p?"max-w-full max-h-[70vh] object-contain":"max-w-full object-contain",style:{imageRendering:"auto"}})]}),e[1]=l.original,e[2]=s,e[3]=y,e[4]=g,e[5]=p,e[6]=f,e[7]=d):d=e[7];let D;e[8]!==l.modified||e[9]!==s||e[10]!==S||e[11]!==y||e[12]!==g||e[13]!==p||e[14]!==f?(D=S&&n.jsxs("div",{className:g,children:[n.jsx("span",{className:"typography-meta text-muted-foreground font-medium",children:f(y?"diffView.image.modified":"diffView.image.new")}),n.jsx("img",{src:l.modified,alt:f("diffView.image.modifiedAlt",{path:s}),className:p?"max-w-full max-h-[70vh] object-contain":"max-w-full object-contain",style:{imageRendering:"auto"}})]}),e[8]=l.modified,e[9]=s,e[10]=S,e[11]=y,e[12]=g,e[13]=p,e[14]=f,e[15]=D):D=e[15];let w;return e[16]!==u||e[17]!==d||e[18]!==D?(w=n.jsx("div",{className:"w-full overflow-auto p-4",style:N,children:n.jsxs("div",{className:u,children:[d,D]})}),e[16]=u,e[17]=d,e[18]=D,e[19]=w):w=e[19],w}),Ns=i.memo(a=>{const e=$e.c(17),{filePath:s,diff:l,renderSideBySide:p,wrapLines:f,hunkActions:y}=a;let S;e[0]!==s?(S=Jn(s)||"text",e[0]=s,e[1]=S):S=e[1];const u=S;if(l.isBinary){let d;return e[2]===Symbol.for("react.memo_cache_sentinel")?(d=n.jsx(ps,{}),e[2]=d):d=e[2],d}if(Ge(s)){let d;return e[3]!==l||e[4]!==s||e[5]!==p?(d=n.jsx(Ss,{filePath:s,diff:l,renderSideBySide:p}),e[3]=l,e[4]=s,e[5]=p,e[6]=d):d=e[6],d}let g;e[7]===Symbol.for("react.memo_cache_sentinel")?(g={contain:"layout"},e[7]=g):g=e[7];let N;return e[8]!==l.fileDiff||e[9]!==l.modified||e[10]!==l.original||e[11]!==s||e[12]!==y||e[13]!==u||e[14]!==p||e[15]!==f?(N=n.jsx("div",{className:"w-full",style:g,children:n.jsx(ns,{original:l.original,modified:l.modified,fileDiff:l.fileDiff,language:u,fileName:s,renderSideBySide:p,wrapLines:f,layout:"inline",hunkActions:y})}),e[8]=l.fileDiff,e[9]=l.modified,e[10]=l.original,e[11]=s,e[12]=y,e[13]=u,e[14]=p,e[15]=f,e[16]=N):N=e[16],N}),ks=i.memo(({directory:a,file:e,layout:s,wrapLines:l,isSelected:p,isExpanded:f,isMounted:y,onSelect:S,onExpandedChange:u,registerSectionRef:g,showOpenInEditorAction:N=!1,isOpeningInEditor:d=!1,onOpenInEditor:D,staged:w=!1,loadFullFiles:k=!1,initialDiffData:m=null,comparisonDiff:A,onRetryComparisonDiff:ne,readOnlyActions:X=!1,hunkActionsEnabled:ue=!1})=>{const{t:F}=Te(),{git:b}=It(),z=ce(i.useCallback(x=>x.directories.get(a)?.diffCache.get(e.path)??null,[a,e.path])),se=ce(x=>x.setDiff),re=ce(x=>x.fetchStatus),pe=le(x=>x.setDiffFileLayout),[Y,de]=i.useState(0),[Q,$]=i.useState(null),[I,P]=i.useState(!1),T=A?A.status==="error"?A.message:null:Q,B=A?A.status==="loading":I,[Z,ee]=i.useState(null),V=i.useRef(!1),[W,he]=i.useState(null),[q,fe]=i.useState(!1),[We,qe]=i.useState(null),[Me,Ve]=i.useState(null),ye=i.useRef(null),ot=i.useRef(null),Ce=i.useMemo(()=>Gt(e),[e]),Be=s==="side-by-side",Ee=k?"full":"patch",c=`${e.index}:${e.working_dir}:${e.insertions}:${e.deletions}`,_=ue&&!X&&!m&&!A&&!Ge(e.path),we=JSON.stringify([Ne(),a,e.path,w,c,Y]),H=W?.scope===we?W.patch:null,K=i.useMemo(()=>A?A.status==="ready"?A.data:null:m||(w?Me:We||(z?{original:z.original,modified:z.modified,isBinary:z.isBinary,contextMode:"full"}:null)),[A,z,m,We,w,Me]),Ue=K?.contextMode===Ee,Je=i.useCallback(x=>{ot.current=x,g(e.path,x)},[e.path,g]),Xe=i.useCallback(x=>{u(e.path,x)},[e.path,u]),Ye=i.useCallback(()=>{S(e.path)},[e.path,S]);i.useEffect(()=>{w?Ve(null):qe(null),$(null),ye.current=null},[c,w]),i.useEffect(()=>{if(!f||!y||Q)return;if(!a||A||m||K&&Ue&&(!_||H!==null)){ye.current=null,P(!1);return}const x=`${a}::${e.path}::${w?"staged":"unstaged"}::${c}::${Ee}::${Y}`;if(ye.current===x)return;ye.current=x,$(null),P(!0);let E=!1;const ie=Ne(),Oe=k?Ht:ft,U=Ge(e.path)?b.getGitFileDiff(a,{path:e.path,staged:w}):!k&&H!==null?Promise.resolve({diff:H}):b.getGitDiff(a,{path:e.path,staged:w,contextLines:Oe}),at=_&&k&&H===null?b.getGitDiff(a,{path:e.path,staged:w,contextLines:ft}).then(C=>C.diff):Promise.resolve(H),Re=Promise.all([U,at]),Fe=ds;let me;const O=new Promise((C,oe)=>{me=setTimeout(()=>oe(new Error(`Timed out after ${Fe}ms`)),Fe)});return Promise.race([Re,O]).then(([C,oe])=>{if(E||ie!==Ne())return;const be="diff"in C&&!k?C.diff:oe;if(_&&k&&be!==null&&/^@@\s/m.test(be)&&"diff"in C&&C.diff!==be&&!kn(C.diff,be))throw he(null),new Error(F("diffView.hunk.unavailable"));if(_&&be!==null&&he({scope:we,patch:be}),"diff"in C){const ve=mt(e.path,C.diff,Ee);w?Ve(ve):qe(ve)}else{const ve={original:C.original??"",modified:C.modified??"",isBinary:C.isBinary,contextMode:"full"};w?Ve(ve):se(a,e.path,ve,ie)}P(!1)}).catch(C=>{if(E||ie!==Ne())return;const oe=C instanceof Error?C.message:String(C);$(oe),P(!1)}).finally(()=>clearTimeout(me)),()=>{E=!0,clearTimeout(me),ye.current===x&&(ye.current=null)}},[H,_,we,A,Ee,K,Ue,Y,a,e.path,c,b,m,f,y,k,Q,se,w,F]);const Qe=i.useCallback(()=>{Xe(!f),Ye()},[Xe,Ye,f]),Ie=i.useCallback(()=>{$(null),he(null),qe(null),Ve(null),ye.current=null,de(x=>x+1)},[]),Ze=i.useCallback(async(x,E)=>{if(!a||!_||B||T||V.current||Z!==null)return;const ie=H?Cn(H,x):null;if(!ie){Ke.error(F("diffView.hunk.unavailable"));return}if(w&&E!=="unstage"||!w&&E==="unstage")return;V.current=!0;const Oe=Ne();ee({index:x,action:E});try{const U=E==="stage"?b.stageGitHunk:E==="unstage"?b.unstageGitHunk:b.revertGitHunk;if(!U){Ke.error(F("diffView.hunk.unsupported"));return}if(await U(a,e.path,ie),Oe!==Ne())return;Ie(),Pt.requestGitRefresh({directory:a,paths:[e.path]}),await re(a,b)}catch(U){if(Oe!==Ne())return;Ie(),Ke.error(U instanceof Error&&U.message?U.message:F("diffView.hunk.unavailable"))}finally{V.current=!1,ee(U=>U?.index===x&&U.action===E?null:U)}},[H,_,B,T,a,re,e.path,b,Z,Ie,w,F]),ge=i.useMemo(()=>_&&H!==null?Rn(H):[],[H,_]),Pe=i.useCallback(x=>n.jsx(as,{index:x,staged:w,busyHunk:Z,disabled:B||!!T,onAction:Ze}),[T,Ze,Z,B,w]),et=i.useMemo(()=>ge.length>0?{anchors:ge,render:Pe}:void 0,[ge,Pe]);return n.jsxs("div",{ref:Je,className:"scroll-mt-9 border-b border-[var(--interactive-border)]/40 last:border-b-0",children:[n.jsx("div",{className:"sticky top-0 z-30 border-b border-[var(--interactive-border)]/35 bg-[var(--surface-elevated)]/90 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--surface-elevated)]/80",children:n.jsxs("div",{role:"button",tabIndex:0,onClick:Qe,onKeyDown:x=>{x.target===x.currentTarget&&(x.key==="Enter"||x.key===" ")&&(x.preventDefault(),Qe())},className:xe("cursor-pointer","group/header relative grid min-h-9 w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-2 overflow-hidden px-3 py-2","bg-transparent","text-muted-foreground hover:text-foreground",p?"bg-[var(--interactive-selection)]/35":null),children:[n.jsx("div",{className:"absolute inset-0 pointer-events-none group-hover/header:bg-[var(--interactive-hover)]/50"}),n.jsxs("div",{className:"relative flex min-w-0 flex-1 items-center gap-2",children:[n.jsx("span",{className:"flex size-5 items-center justify-center opacity-70 group-hover/header:opacity-100",children:f?n.jsx(M,{name:"arrow-down-s",className:"size-4"}):n.jsx(M,{name:"arrow-right-s",className:"size-4"})}),n.jsx("span",{className:"typography-micro font-semibold leading-none w-4 text-center uppercase",style:{color:Ce.color},title:F(Ce.descriptionKey),"aria-label":F(Ce.descriptionKey),children:Ce.code}),n.jsx("span",{className:"min-w-0 flex-1 overflow-hidden typography-ui-label",title:e.path,children:n.jsxs("span",{className:"flex min-w-0 items-center gap-2",children:[n.jsx(zt,{filePath:e.path,className:"h-3.5 w-3.5 flex-shrink-0 align-middle"}),(()=>{const x=e.path.lastIndexOf("/");if(x===-1)return n.jsx("span",{className:"block min-w-0 truncate typography-ui-label text-foreground",style:{direction:"rtl",textAlign:"left",unicodeBidi:"plaintext"},children:e.path});const E=e.path.slice(0,x),ie=e.path.slice(x+1);return n.jsxs("span",{className:"flex min-w-0 items-baseline overflow-hidden",children:[n.jsx("span",{className:"min-w-0 truncate typography-ui-label text-muted-foreground",style:{direction:"rtl",textAlign:"left",unicodeBidi:"plaintext"},children:E}),n.jsxs("span",{className:"flex-shrink-0 typography-ui-label",children:[n.jsx("span",{className:"text-muted-foreground",children:"/"}),n.jsx("span",{className:"text-foreground",children:ie})]})]})})()]})})]}),n.jsxs("div",{className:"relative flex shrink-0 items-center justify-self-end gap-2",children:[$t(e.insertions,e.deletions),N&&D?n.jsx(ke,{variant:"ghost",size:"sm",className:"h-5 w-5 p-0 opacity-70 hover:opacity-100",title:F("diffView.actions.openFileInEditorAtChange"),onClick:x=>{x.stopPropagation(),D(e.path,K)},disabled:d,children:d?n.jsx(M,{name:"loader-4",className:"size-3.5 animate-spin"}):n.jsx(M,{name:"edit",className:"size-3.5"})}):null,n.jsx(Ot,{mode:Be?"side-by-side":"unified",onModeChange:x=>{const E=x==="side-by-side"?"side-by-side":"inline";pe(e.path,E)},className:"opacity-70"})]})]})}),f&&n.jsxs("div",{className:"relative bg-background overflow-hidden",children:[!y&&!T?n.jsx("div",{className:"h-40 border border-border/40 bg-background/40"}):null,T?n.jsxs("div",{className:"flex flex-col items-center gap-2 px-4 py-8 text-sm text-muted-foreground",children:[n.jsx("div",{className:"typography-ui-label font-semibold text-foreground",children:F("diffView.state.failedToLoadDiff")}),n.jsx("div",{className:"typography-meta text-muted-foreground max-w-[32rem] text-center",children:T}),n.jsx("button",{type:"button",className:"typography-ui-label text-primary hover:underline",onClick:()=>A?ne?.():Ie(),children:F("diffView.actions.retry")})]}):null,y&&B&&!K&&!T?n.jsxs("div",{className:"flex items-center justify-center gap-2 px-4 py-8 text-sm text-muted-foreground",children:[n.jsx(M,{name:"loader-4",className:"size-4 animate-spin"}),F("diffView.state.loadingDiff")]}):null,y&&K&&!q&&e.insertions+e.deletions>At?n.jsxs("div",{className:"flex flex-col items-center gap-2 px-4 py-8 text-sm text-muted-foreground",children:[n.jsx("div",{className:"typography-ui-label font-semibold text-foreground",children:F("diffView.state.largeDiff",{count:e.insertions+e.deletions})}),n.jsx("div",{className:"typography-meta text-muted-foreground",children:F("diffView.state.largeDiffDescription")}),n.jsx("button",{type:"button",className:"typography-ui-label text-primary hover:underline",onClick:()=>fe(!0),children:F("diffView.actions.renderAnyway")})]}):null,y&&K&&(q||e.insertions+e.deletions<=At)?n.jsx(n.Fragment,{children:n.jsx(Ns,{filePath:e.path,diff:K,renderSideBySide:Be,wrapLines:l,hunkActions:et})}):null]})]})}),Hi=({hideStackedFileSidebar:a=!1,stackedDefaultCollapsedAll:e=!1,pinSelectedFileHeaderToTopOnNavigate:s=!1,showOpenInEditorAction:l=!1,diffScope:p="all",onDiffScopeChange:f,targetFilePath:y=null,flushContent:S=!1})=>{const{t:u}=Te(),{git:g,files:N}=It(),d=Bn(),{rootIsGitRepo:D,gitDirectory:w,nestedRepos:k}=In(d??null),m=w??d,A=le(t=>t.openContextSurface),ne=ss(t=>t.requestSource),{screenWidth:X,isMobile:ue}=Pn(),F=On(m??null),b=zn(m??null),z=_n(m??null),se=ce(t=>t.setActiveDirectory),re=ce(t=>t.ensureStatus),pe=ce(t=>t.selectNestedRepo),Y=ce(t=>t.fetchStatus),de=ce(t=>t.fetchBranches),Q=ce(t=>t.clearDiffCache),$=ce(t=>t.setDiff),[I,P]=i.useState(null),[T,B]=i.useState(!1),[Z,ee]=i.useState(null),[V,W]=i.useState(()=>new Set),[he,q]=i.useState(()=>new Set),[fe,We]=i.useState(!1),[qe,Me]=i.useState(0),[Ve,ye]=i.useState(()=>new Map),[ot,Ce]=i.useState(!1),[Be,Ee]=i.useState(!1),[c,_]=i.useState(p);i.useEffect(()=>{_(p)},[p]);const we=le(t=>t.pendingDiffFile),H=le(t=>t.pendingDiffStaged),K=le(t=>t.pendingDiffScope),Ue=le(t=>t.setPendingDiffFile),Je=le(t=>t.diffLayoutPreference),Xe=le(t=>t.diffFileLayout),Ye=le(t=>t.setDiffFileLayout),Qe=le(t=>t.diffWrapLines),Ie=le(t=>t.setDiffWrapLines),Ze=le(t=>t.openContextFileAtLine),ge=Rt(t=>t.currentSessionId),Pe=Hn(ge??"",d??void 0),et=Qe,x=c==="staged"?!0:c==="working"?!1:null,E=x??T,ie=ue||X<=768,Oe=!!ge&&c!=="turn"&&c!=="commit"&&!ie&&!Ae(),U=c!=="turn"&&!ie&&!Ae(),at=!a&&!ie&&X>=1024,Re=i.useRef(null),Fe=i.useRef(new Map),me=i.useRef(null),O=i.useRef(null),C=i.useRef(!1),oe=i.useRef(null),be=i.useRef(null),ve=i.useRef(null),lt=i.useRef(null),pt=i.useCallback(()=>{const t=Re.current;if(!t)return null;const r=t.getBoundingClientRect().top,o=[];for(const[h,v]of Fe.current)v&&o.push({path:h,top:v.getBoundingClientRect().top});return Ft(r,o)},[]),ze=i.useCallback(()=>{me.current=null,C.current=!1,ee(null),O.current!==null&&(window.cancelAnimationFrame(O.current),O.current=null)},[]),_e=i.useCallback(t=>{W(r=>{if(r.has(t))return r;const o=new Set(r);return o.add(t),o})},[]),tt=i.useMemo(()=>{for(let t=Pe.length-1;t>=0;t-=1){const r=Pe[t];if(r.role==="user")return ws(r.summary?.diffs)}return[]},[Pe]),Wt=i.useMemo(()=>{const t=new Map;for(const r of tt)if(r.file){if(typeof r.patch=="string"){t.set(r.file,mt(r.file,r.patch,"patch"));continue}t.set(r.file,{original:r.before??"",modified:r.after??"",contextMode:"full"})}return t},[tt]),qt=i.useMemo(()=>b?.files?b.files.filter(Tt).length:0,[b]),Ut=i.useMemo(()=>b?.files?b.files.filter(Lt).length:0,[b]),Jt=tt.length,J=b?.current??null,De=bn(m??null,J,c==="commit"&&!Ae()),Le=De.selectedCommit?.hash??null;i.useEffect(()=>{c==="commit"&&Ae()&&(_("working"),f?.("working"))},[c,f]);const He=ce(t=>m?t.directories.get(m)?.branches??null:null),Xt=ce(t=>m?t.directories.get(m)?.isLoadingBranches??!1:!1),Yt=i.useCallback(()=>{m&&de(m,g)},[m,de,g]),Qt=An(m??null,F!==!1,Xt,!!He,Yt,ms),ht=i.useMemo(()=>{const t=b?.tracking?.trim().split("/")[0];return(t&&He?.defaultBranches?.[t])??He?.defaultBranches?.origin??null},[He,b?.tracking]),gt=!Ae()&&Vn(J,ht),Zt=b!==null,xt=Ae()||Qt||En(J,ht,Zt,He!==null),en=vn(t=>t.setOverride),{base:je,resolved:tn,revision:yt}=jn(m??null,J,gt&&c==="branch"),[nn,sn]=i.useState(0);i.useEffect(()=>{const t=Mn(c,!xt);t!==c&&(_(t),t==="working"&&f?.("working"))},[c,xt,f]);const rn=i.useMemo(()=>c==="commit"&&Le?{kind:"commit",hash:Le}:c==="branch"&&je&&J?{kind:"branch",baseRef:je,headRef:J}:null,[c,je,J,Le]),Se=Ln(m??null,rn,!Ae(),c==="branch"?yt:""),{fetchDiff:wt}=Se,st=c==="commit"?Se.files:null,bt=c==="commit"?Se.error:null,rt=c==="branch"?Se.files:null,vt=c==="branch"?Se.error:null,jt=Se.files?Se.key:null,on=i.useMemo(()=>c==="branch"||c==="commit"?Array.from(V).sort().join("\0"):"",[c,V]),an=i.useCallback(async t=>{try{const r=await wt(t,fe?Ht:ft);return{status:"ready",data:mt(t,r.diff,fe?"full":"patch")}}catch(r){return{status:"error",message:r instanceof Error?r.message:u("diffView.state.failedToLoadDiff")}}},[wt,fe,u]),ln=Tn(jt,on,jt?an:null,Et,JSON.stringify([c==="branch"?yt:"",nn,fe])),cn=rt?.length??null,j=i.useMemo(()=>{if(c==="commit")return(st??[]).map(o=>({path:o.path,index:"",working_dir:o.status,insertions:o.insertions,deletions:o.deletions,isNew:o.status==="A"}));if(c==="branch")return(rt??[]).map(o=>({path:o.path,index:"",working_dir:o.status,insertions:0,deletions:0,isNew:o.status==="A"})).sort((o,h)=>o.path.localeCompare(h.path));if(c==="turn")return tt.map(o=>({path:o.file??"",index:"",working_dir:bs(o.status),insertions:o.additions??0,deletions:o.deletions??0,isNew:o.status==="added"})).filter(o=>o.path).sort((o,h)=>o.path.localeCompare(h.path));if(!b?.files)return[];const t=b.diffStats??{},r=c==="staged"?Lt:c==="working"?Tt:()=>!0;return b.files.filter(r).map(o=>({...o,insertions:t[o.path]?.insertions??0,deletions:t[o.path]?.deletions??0,isNew:xs(o)})).sort((o,h)=>o.path.localeCompare(h.path))},[c,rt,st,tt,b]),ct=i.useMemo(()=>j.map(t=>t.path).join("\0"),[j]);i.useEffect(()=>{const t=ct?ct.split("\0"):[],r=new Set(t),o=`${m??""}:${c}:${e?"collapsed":"default"}`,h=be.current!==o;be.current=o,W(v=>{if(h){const L=e?0:fs(t.length);return new Set(t.slice(0,L))}let G=!1;const R=new Set;for(const L of v){if(!r.has(L)){G=!0;continue}R.add(L)}return G?R:v}),q(v=>{if(h)return new Set;let G=!1;const R=new Set;for(const L of v){if(!r.has(L)){G=!0;continue}R.add(L)}return G?R:v})},[c,ct,m,e]);const St=i.useCallback(()=>{oe.current=null;const t=Re.current;if(!t)return;const r=t.getBoundingClientRect(),o=r.top-Vt,h=r.bottom+Vt,v={},G=[];for(const[R,L]of Fe.current){if(!L)continue;const te=L.getBoundingClientRect();G.push({path:R,top:te.top}),V.has(R)&&(te.bottom<o||te.top>h||(v[R]=!0))}ve.current=Ft(r.top,G),q(R=>{let L=!1;const te=new Set(R);for(const Ct of Object.keys(v))te.has(Ct)||(te.add(Ct),L=!0);return L?te:R})},[V]),ae=i.useCallback(()=>{typeof window>"u"||oe.current===null&&(oe.current=window.requestAnimationFrame(St))},[St]);i.useEffect(()=>{const t=Re.current;if(t)return ae(),t.addEventListener("scroll",ae,{passive:!0}),window.addEventListener("resize",ae),()=>{t.removeEventListener("scroll",ae),window.removeEventListener("resize",ae),oe.current!==null&&(window.cancelAnimationFrame(oe.current),oe.current=null)}},[j,V,ae]);const ut=i.useCallback(t=>{const r=Xe[t.path];if(r)return r;if(Je==="inline")return"inline";if(Je==="side-by-side")return"side-by-side";const o=X<us;return t.isNew||o?"inline":"side-by-side"},[Xe,Je,X]),Nt=i.useMemo(()=>j.length===0?null:j.every(t=>ut(t)==="side-by-side")?"side-by-side":"inline",[j,ut]);i.useEffect(()=>{m&&(se(m),re(m,g))},[m,se,re,g]),i.useEffect(()=>{if(m)return Pt.onGitRefreshHint(t=>{Mt(t.directory)===Mt(m)&&(t.paths?.length&&(lt.current=pt()??ve.current,Q(m,t.paths),ye(r=>{const o=new Map(r);for(const h of t.paths??[])o.set(h,(o.get(h)??0)+1);return o})),Y(m,g,{silent:!0}))})},[pt,Q,m,Y,g]),i.useLayoutEffect(()=>{const t=lt.current;if(!t)return;lt.current=null;const r=Re.current,o=Fe.current.get(t.path);if(!r||!o)return;const h=r.getBoundingClientRect().top,v=o.getBoundingClientRect().top-h;r.scrollTop=cs(r.scrollTop,t.topOffset,v,r.scrollHeight-r.clientHeight),ve.current=t},[Ve]),i.useEffect(()=>{c!=="all"&&!K||we&&(K&&_(K),P(we),B(K==="staged"||!K&&H),Ue(null),C.current=!0,me.current=we,_e(we),Me(t=>t+1))},[c,_e,we,K,H,Ue]),i.useEffect(()=>{if(c==="all")return;const t=y?.trim();t&&(P(t),B(c==="staged"),C.current=!0,me.current=t,_e(t),Me(r=>r+1))},[c,_e,y]),i.useEffect(()=>{if(!I)return;j.some(r=>r.path===I)||(P(null),B(!1))},[j,I]);const un=i.useCallback((t,r)=>{const o=Fe.current;r?o.set(t,r):o.delete(t),ae()},[ae]),dn=i.useCallback((t,r)=>{ze(),W(o=>{const h=o.has(t);if(r===h)return o;const v=new Set(o);return r?v.add(t):v.delete(t),v}),r||q(o=>{if(!o.has(t))return o;const h=new Set(o);return h.delete(t),h}),ae()},[ze,ae]),fn=i.useCallback(()=>{ze(),W(t=>t.size>0?new Set:new Set(j.map(r=>r.path))),q(new Set),ae()},[ze,j,ae]),mn=i.useCallback(async t=>{if(!ge)return;const r=Rt.getState().getDirectoryForSession(ge)||d||"";if(!r){Ke.error(u("diffView.reviewDialog.toast.noSessionDirectory"));return}Ee(!0);try{await Kn({originalSessionID:ge,directory:r,providerID:t.providerID,modelID:t.modelID,agent:t.agent||void 0,variant:t.variant||void 0,generateHandoff:t.generateHandoff,returnAfterHandoffRequest:t.generateHandoff,autoReview:t.autoReview}),Ce(!1)}catch(o){console.error("[review-flow] failed to start review flow",o),Ke.error(o instanceof Error?o.message:u("diffView.reviewDialog.toast.startFailed"))}finally{Ee(!1)}},[ge,d,u]),it=i.useCallback(t=>{const r=Fe.current.get(t),o=Re.current;if(!r||!o)return!1;const h=r.getBoundingClientRect().top-o.getBoundingClientRect().top;return o.scrollTo({top:o.scrollTop+h,behavior:"auto"}),!0},[]);i.useEffect(()=>{if(!me.current)return;let r=0;const o=20;let h=!1;const v=(R=!0)=>{h||(h=!0,me.current=null,C.current=!1,R&&ee(null),O.current!==null&&(window.cancelAnimationFrame(O.current),O.current=null))},G=()=>{if(h){O.current=null;return}const R=me.current;if(!R){v(),O.current=null;return}if(!it(R)){r+=1,r<o?O.current=window.requestAnimationFrame(G):(v(),O.current=null);return}if(s&&C.current){ee(R),v(!1);return}v()};return O.current=window.requestAnimationFrame(G),()=>{h=!0,O.current!==null&&(window.cancelAnimationFrame(O.current),O.current=null)}},[s,qe,it]);const pn=i.useCallback(t=>{},[]),dt=i.useCallback(t=>{ze(),P(t),B(!1),C.current=!0,me.current=t,_e(t),Me(r=>r+1),it(t)},[ze,_e,it]);i.useEffect(()=>{const t=r=>{if(!r.altKey||r.metaKey||r.ctrlKey||r.shiftKey||r.key!=="ArrowDown"&&r.key!=="ArrowUp")return;const o=r.target;if(o instanceof HTMLElement&&(o.isContentEditable||o.tagName==="INPUT"||o.tagName==="TEXTAREA"||o.closest('[role="dialog"]'))||j.length===0)return;const h=r.key==="ArrowDown"?1:-1,v=I?j.findIndex(L=>L.path===I):-1,G=v===-1?h>0?0:j.length-1:v+h,R=j[G];R&&(r.preventDefault(),dt(R.path))};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[j,I,dt]);const hn=i.useCallback(t=>{const r=t==="side-by-side"?"side-by-side":"inline";j.forEach(o=>{Ye(o.path,r)})},[j,Ye]),[gn,kt]=i.useState(null),xn=i.useCallback(async(t,r)=>{if(!m||!t)return;kt(t);const o=Ne();try{let h=null;if(r?.patch&&!r.isBinary&&!Ge(t)?h=Dt(r.patch):r&&r.contextMode==="full"&&!r.isBinary&&!Ge(t)&&(h=Bt(r.original,r.modified)),h===null)try{const te=await g.getGitDiff(m,{path:t,staged:E,contextLines:3});h=Dt(te.diff)}catch{h=null}let v=r;if(h===null||!v){const te=await g.getGitFileDiff(m,{path:t,staged:E});v={original:te.original??"",modified:te.modified??"",isBinary:te.isBinary},E||$(m,t,v,o)}const G=h??(v.isBinary||Ge(t)?1:Bt(v.original,v.modified)),R=ys(m,t),L=await rs(N,R,{directory:m});if(!L.ok){Ke.error(is(L.reason));return}Ze(m,R,G,1)}finally{kt(h=>h===t?null:h)}},[E,m,N,g,Ze,$]),yn=()=>{if(!m)return null;const t=r=>x!==null?x:T&&r===I;return n.jsxs("div",{className:xe("flex min-w-0 flex-1 min-h-0 h-full",S?"gap-0":"gap-3 px-3 pb-3 pt-2"),children:[at&&n.jsxs("section",{className:"hidden lg:flex w-72 flex-col rounded-xl border border-border/60 bg-background/70 overflow-hidden",children:[n.jsxs("div",{className:"flex items-center justify-between px-3 py-1.5 border-b border-border/40",children:[n.jsx("span",{className:"typography-ui-header font-semibold text-foreground",children:u("diffView.section.files")}),n.jsx("span",{className:"typography-meta text-muted-foreground",children:j.length})]}),n.jsx(js,{changedFiles:j,selectedFile:null,onSelectFile:dt})]}),n.jsx("div",{className:"relative flex-1 min-w-0 min-h-0 h-full",children:n.jsx(_t,{ref:Re,outerClassName:"min-h-0 h-full",className:"[overflow-anchor:none] pb-16",disableHorizontal:!0,observeMutations:!1,preventOverscroll:!0,"data-diff-virtual-root":!0,children:n.jsx("div",{className:"flex flex-col [overflow-anchor:none]","data-diff-virtual-content":!0,children:j.map(r=>n.jsx(ks,{directory:m,file:r,layout:ut(r),wrapLines:et,isSelected:!1,isExpanded:V.has(r.path),isMounted:he.has(r.path)||r.path===Z,onSelect:pn,onExpandedChange:dn,registerSectionRef:un,showOpenInEditorAction:l&&c!=="turn",isOpeningInEditor:gn===r.path,onOpenInEditor:(o,h)=>{xn(o,h)},staged:t(r.path),loadFullFiles:fe,readOnlyActions:c==="branch"||c==="commit",hunkActionsEnabled:c==="all"||c==="working"||c==="staged",comparisonDiff:c==="branch"||c==="commit"?ln.get(r.path)??Et:void 0,onRetryComparisonDiff:()=>sn(o=>o+1),initialDiffData:c==="turn"?Wt.get(r.path)??null:null},`${Ne()}:${m}:${r.path}:${Ve.get(r.path)??0}`))})})})]})},wn=()=>{if(!m)return n.jsx("div",{className:"flex flex-1 items-center justify-center text-sm text-muted-foreground",children:u("diffView.state.selectSessionDirectory")});if(c!=="turn"&&z&&!b)return n.jsxs("div",{className:"flex flex-1 items-center justify-center gap-2 text-sm text-muted-foreground",children:[n.jsx(M,{name:"loader-4",className:"size-4 animate-spin"}),u("diffView.state.loadingRepositoryStatus")]});if(c!=="turn"&&F===!1)return n.jsx("div",{className:"flex flex-1 items-center justify-center text-sm text-muted-foreground",children:u("diffView.state.notGitRepository")});if(c==="commit"){if(bt)return n.jsxs("div",{className:"flex flex-1 flex-col items-center justify-center gap-3 p-6 text-center",children:[n.jsx("p",{className:"typography-meta text-muted-foreground",children:bt}),n.jsx(ke,{variant:"outline",size:"sm",onClick:()=>{Se.refresh()},children:u("diffView.actions.retry")})]});if(!Le&&!De.loading)return n.jsx("div",{className:"flex flex-1 items-center justify-center text-sm text-muted-foreground",children:De.error??u("commitComparison.noCommits")});if(!st)return n.jsxs("div",{className:"flex flex-1 items-center justify-center gap-2 text-sm text-muted-foreground",children:[n.jsx(M,{name:"loader-4",className:"size-4 animate-spin"}),u("diffView.state.loadingDiff")]})}if(c==="branch"){if(!tn)return n.jsxs("div",{className:"flex flex-1 items-center justify-center gap-2 text-sm text-muted-foreground",children:[n.jsx(M,{name:"loader-4",className:"size-4 animate-spin"}),u("diffView.branch.resolvingBase")]});if(!je)return n.jsxs("div",{className:"flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center",children:[n.jsx(M,{name:"git-branch",className:"size-6 text-muted-foreground"}),n.jsx("div",{className:"typography-ui-label font-semibold text-foreground",children:u("diffView.branch.noBaseTitle")}),n.jsx("div",{className:"max-w-sm typography-micro text-muted-foreground",children:u("gitView.pr.toast.baseBranchRequired")})]});if(vt)return n.jsxs("div",{className:"flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center",children:[n.jsx("div",{className:"typography-ui-label font-semibold text-foreground",children:u("diffView.branch.loadError")}),n.jsx("div",{className:"max-w-sm typography-micro text-muted-foreground",children:vt}),n.jsx(ke,{variant:"outline",size:"sm",onClick:()=>{Se.refresh()},children:u("diffView.actions.retry")})]});if(rt===null)return n.jsxs("div",{className:"flex flex-1 items-center justify-center gap-2 text-sm text-muted-foreground",children:[n.jsx(M,{name:"loader-4",className:"size-4 animate-spin"}),u("diffView.branch.loadingFiles")]})}return j.length===0?n.jsx("div",{className:"flex flex-1 items-center justify-center text-sm text-muted-foreground",children:c==="turn"?u("diffView.state.noLastTurnChanges"):c==="commit"?u("commitComparison.emptyDiff"):c==="branch"&&je?u("diffView.branch.empty",{base:ts(je)}):u("diffView.state.cleanWorkingTree")}):yn()};return n.jsxs("div",{className:"flex h-full flex-col overflow-hidden bg-background",children:[n.jsxs("div",{className:"@container/diff-toolbar flex min-w-0 items-center gap-2 px-3 py-2 bg-background",children:[D===!1&&Array.isArray(k)&&k.length>0?n.jsx(es,{repositories:k,selectedRepository:w??null,onSelectRepository:t=>{d&&pe(d,t)},repositoryRoot:d??void 0}):null,!ue&&(c!=="all"?n.jsx(vs,{scope:c,workingCount:qt,stagedCount:Ut,turnCount:Jt,branchCount:cn,commitCount:st?.length??null,showCommitOption:!Ae(),showBranchOption:gt,onScopeChange:t=>{_(t),f?.(t)}}):n.jsx("div",{className:"flex items-center gap-1 rounded-md px-2 py-1 text-muted-foreground shrink-0",children:n.jsx("span",{className:"typography-ui-label font-semibold text-foreground",children:z&&!b?u("diffView.state.loadingChanges"):j.length===1?u("diffView.summary.changedFilesSingle",{count:j.length}):u("diffView.summary.changedFilesPlural",{count:j.length})})})),c==="branch"&&n.jsx(Sn,{branches:He?.all??[],currentBranch:J,base:je,onSelect:t=>{m&&J&&en(m,J,t)}},JSON.stringify([m,J])),c==="commit"&&n.jsx(Nn,{commits:De.commits,selectedHash:Le,loading:De.loading,error:De.error,onSelect:De.select,onRefresh:()=>{De.refresh()}},JSON.stringify([m,J])),j.length>0&&n.jsxs(ke,{variant:"ghost",size:"sm",onClick:fn,className:xe("diff-toolbar__expand-button h-7 flex-shrink-0 gap-1 px-1.5 text-muted-foreground hover:text-foreground","ml-auto"),title:V.size>0?u("diffView.actions.collapseAll"):u("diffView.actions.expandAll"),children:[n.jsx(M,{name:"expand-up-down",className:"size-4"}),n.jsx("span",{className:"diff-toolbar__expand-label typography-ui-label",children:V.size>0?u("diffView.actions.collapseAll"):u("diffView.actions.expandAll")})]}),j.length>0&&Oe&&n.jsxs(ke,{variant:"default",size:"sm",onClick:()=>Ce(!0),disabled:Be,className:"diff-toolbar__review-button h-7 flex-shrink-0 gap-1.5 px-2","aria-label":u("diffView.actions.reviewAria"),children:[Be?n.jsx(M,{name:"loader-4",className:"size-4 animate-spin"}):n.jsx(M,{name:"search-eye",className:"size-4"}),n.jsx("span",{className:"diff-toolbar__review-label typography-ui-label",children:u("diffView.actions.review")})]}),j.length>0&&U&&n.jsxs(ke,{variant:"outline",size:"sm",onClick:()=>{const t=m??"";ne(t,c==="commit"&&Le?{kind:"commit",hash:Le}:c==="branch"&&je&&J?{kind:"branch",baseRef:je,headRef:J}:{kind:"working-tree",scope:c==="staged"||c==="working"?c:"all"}),A(t,"walkthrough")},className:xe("diff-toolbar__walkthrough-button h-7 flex-shrink-0 gap-1.5 px-2",os),"aria-label":u("walkthrough.action.open"),children:[n.jsx(M,{name:"route",className:"size-4"}),n.jsx("span",{className:"diff-toolbar__walkthrough-label typography-ui-label",children:u("walkthrough.action.open")})]}),j.length>0&&n.jsxs(Gn,{children:[n.jsx($n,{asChild:!0,children:n.jsx(ke,{variant:"ghost",size:"sm",onClick:()=>We(t=>!t),"aria-pressed":fe,"aria-label":u(fe?"diffView.actions.disableFullFiles":"diffView.actions.loadFullFiles"),className:xe("h-7 w-7 flex-shrink-0 p-0 text-muted-foreground hover:text-foreground",fe&&"bg-interactive-selection text-interactive-selection-foreground"),children:n.jsx(M,{name:"file-download",className:"size-4"})})}),n.jsx(Wn,{children:n.jsx("p",{children:u(fe?"diffView.actions.disableFullFiles":"diffView.actions.loadFullFiles")})})]}),j.length>0&&n.jsx(ke,{variant:"ghost",size:"sm",onClick:()=>Ie(!Qe),className:xe("h-5 w-5 p-0 transition-opacity",et?"text-foreground opacity-100":"text-muted-foreground opacity-60 hover:opacity-100"),title:u(et?"diffView.actions.disableLineWrap":"diffView.actions.enableLineWrap"),children:n.jsx(M,{name:"text-wrap",className:"size-4"})}),Nt&&n.jsx(Ot,{mode:Nt==="side-by-side"?"side-by-side":"unified",onModeChange:hn})]}),n.jsx(qn,{open:ot,onOpenChange:Ce,projectDirectory:m??null,submitting:Be,onConfirm:mn}),wn()]})};export{Hi as DiffView,ks as MultiFileDiffEntry};