@bud.sh/app 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/dist/client/assets/{index-sj6mWw9-.js → index-GPPa1XPO.js} +2 -2
  2. package/dist/client/assets/workspace-CznMN7F6.js +2 -0
  3. package/dist/client/index.html +1 -1
  4. package/dist/hosted/hosted.js +233 -38
  5. package/dist/node/index.js +95 -0
  6. package/dist/node-host/assets/abap-LtYK6g-v.js +3 -0
  7. package/dist/node-host/assets/actionscript-3-CfsDl31I.js +3 -0
  8. package/dist/node-host/assets/ada-CvTdxb4T.js +3 -0
  9. package/dist/node-host/assets/ahk-HN_I_y_3.js +3 -0
  10. package/dist/node-host/assets/ahk2-bJ5Wm9-K.js +3 -0
  11. package/dist/node-host/assets/andromeeda-BASpqCeJ.js +4 -0
  12. package/dist/node-host/assets/angular-html-C4SmZqv0.js +33 -0
  13. package/dist/node-host/assets/angular-ts-X1i_WOxI.js +27 -0
  14. package/dist/node-host/assets/apache-BIl2qPOf.js +3 -0
  15. package/dist/node-host/assets/apex-CybrBQ0z.js +3 -0
  16. package/dist/node-host/assets/api_vite-DSBC5Nzy.js +5 -0
  17. package/dist/node-host/assets/apl-CDTyNgE2.js +17 -0
  18. package/dist/node-host/assets/applescript-CJ80B5XJ.js +3 -0
  19. package/dist/node-host/assets/ara-IxnQjO6j.js +3 -0
  20. package/dist/node-host/assets/asciidoc-xKuVlNk6.js +3 -0
  21. package/dist/node-host/assets/asm-wBDSgBbA.js +3 -0
  22. package/dist/node-host/assets/astro-BiZFCPSt.js +19 -0
  23. package/dist/node-host/assets/aurora-x-DugsLcii.js +4 -0
  24. package/dist/node-host/assets/awk-D6dA05Ye.js +3 -0
  25. package/dist/node-host/assets/ayu-dark-BT3qdi3o.js +4 -0
  26. package/dist/node-host/assets/ayu-light-BVZyHoh9.js +4 -0
  27. package/dist/node-host/assets/ayu-mirage-XFwUVZQZ.js +4 -0
  28. package/dist/node-host/assets/ballerina-DIdXD2KH.js +3 -0
  29. package/dist/node-host/assets/bat-Dx8nnxXE.js +3 -0
  30. package/dist/node-host/assets/beancount-BG2UQN2D.js +3 -0
  31. package/dist/node-host/assets/berry-CgA2ea0_.js +3 -0
  32. package/dist/node-host/assets/bibtex-BzgdqG3s.js +3 -0
  33. package/dist/node-host/assets/bicep-CZ338dSk.js +3 -0
  34. package/dist/node-host/assets/bird2-BMprQDNV.js +3 -0
  35. package/dist/node-host/assets/blade-cZjv4Qad.js +21 -0
  36. package/dist/node-host/assets/bsl-Bh8bzcoy.js +6 -0
  37. package/dist/node-host/assets/build-Dn-L17Oj.js +5372 -0
  38. package/dist/node-host/assets/c-BXsbGxlp.js +6 -0
  39. package/dist/node-host/assets/c3-u0qNrniV.js +3 -0
  40. package/dist/node-host/assets/cadence-B1ELWULD.js +3 -0
  41. package/dist/node-host/assets/cairo-BsQhEq3R.js +6 -0
  42. package/dist/node-host/assets/canvas-BZhzizzt.js +1066 -0
  43. package/dist/node-host/assets/catppuccin-frappe-D20KODDN.js +4 -0
  44. package/dist/node-host/assets/catppuccin-latte-eGLVHIUJ.js +4 -0
  45. package/dist/node-host/assets/catppuccin-macchiato-bQGch4PY.js +4 -0
  46. package/dist/node-host/assets/catppuccin-mocha-Bes8-0Mr.js +4 -0
  47. package/dist/node-host/assets/chapel-cEZNBFmv.js +6 -0
  48. package/dist/node-host/assets/clarity-BkOxTQMf.js +3 -0
  49. package/dist/node-host/assets/clojure-DDsQ0FaV.js +3 -0
  50. package/dist/node-host/assets/cloneFileDiffMetadata-C1BcNpxN.js +3075 -0
  51. package/dist/node-host/assets/cmake-DPsCB_V6.js +3 -0
  52. package/dist/node-host/assets/cobol-BpMD18Rr.js +11 -0
  53. package/dist/node-host/assets/codeowners-CxEKPWu0.js +3 -0
  54. package/dist/node-host/assets/codeql-BwdvwJnv.js +3 -0
  55. package/dist/node-host/assets/coffee-BX_u-2R1.js +6 -0
  56. package/dist/node-host/assets/comment-data-nUlI0szN.js +92501 -0
  57. package/dist/node-host/assets/common-lisp-DJtwvW4V.js +3 -0
  58. package/dist/node-host/assets/coq-Byd42hNw.js +3 -0
  59. package/dist/node-host/assets/cpp-DjJRL98I.js +22 -0
  60. package/dist/node-host/assets/crystal-Cz-dKioR.js +19 -0
  61. package/dist/node-host/assets/csharp-Cx443ZdC.js +3 -0
  62. package/dist/node-host/assets/css-CWNyyZRg.js +6 -0
  63. package/dist/node-host/assets/csv-DqIVFkK7.js +3 -0
  64. package/dist/node-host/assets/cue-B5KneM5p.js +3 -0
  65. package/dist/node-host/assets/cypher-D8Bb8Cq2.js +3 -0
  66. package/dist/node-host/assets/d-BQY8TDgQ.js +3 -0
  67. package/dist/node-host/assets/dark-plus-BGuiqa1B.js +4 -0
  68. package/dist/node-host/assets/dart-BZ3YsgDB.js +3 -0
  69. package/dist/node-host/assets/dax-S-Noo-ku.js +3 -0
  70. package/dist/node-host/assets/desktop-BgOb7Aj4.js +3 -0
  71. package/dist/node-host/assets/diff-Blb4MjsC.js +3 -0
  72. package/dist/node-host/assets/dist-CVXhIZqO.js +6659 -0
  73. package/dist/node-host/assets/docker-PC1tuYpa.js +3 -0
  74. package/dist/node-host/assets/dotenv-BNWDqzYB.js +3 -0
  75. package/dist/node-host/assets/dracula-0CVA2XY5.js +4 -0
  76. package/dist/node-host/assets/dracula-soft-Dk9bpbVU.js +4 -0
  77. package/dist/node-host/assets/dream-maker-CngiPd8U.js +3 -0
  78. package/dist/node-host/assets/edge-D1_nghia.js +13 -0
  79. package/dist/node-host/assets/edit-DjpTbnEc.js +9788 -0
  80. package/dist/node-host/assets/elixir-x6BMZ6zW.js +6 -0
  81. package/dist/node-host/assets/elm-CiAjb3qf.js +6 -0
  82. package/dist/node-host/assets/emacs-lisp-DPCJASlD.js +3 -0
  83. package/dist/node-host/assets/erb-DzXTnEFa.js +11 -0
  84. package/dist/node-host/assets/erlang-DALBVNgL.js +6 -0
  85. package/dist/node-host/assets/everforest-dark-CRjTbhh5.js +4 -0
  86. package/dist/node-host/assets/everforest-light-CEeYFLn8.js +4 -0
  87. package/dist/node-host/assets/fennel-B3wtZRne.js +3 -0
  88. package/dist/node-host/assets/files-CTzcWGjb.js +3771 -0
  89. package/dist/node-host/assets/fish-D7qAeWQz.js +3 -0
  90. package/dist/node-host/assets/fluent-BjfpB34g.js +3 -0
  91. package/dist/node-host/assets/fortran-fixed-form-BbzsUHx4.js +6 -0
  92. package/dist/node-host/assets/fortran-free-form-yxJ7igsv.js +3 -0
  93. package/dist/node-host/assets/fsharp-D2o1Vyx0.js +6 -0
  94. package/dist/node-host/assets/gdresource-aTcOcjSd.js +11 -0
  95. package/dist/node-host/assets/gdscript-CSSSWtkL.js +3 -0
  96. package/dist/node-host/assets/gdshader-D7JfdFV3.js +3 -0
  97. package/dist/node-host/assets/genie-DnK2KWIP.js +3 -0
  98. package/dist/node-host/assets/gherkin-D6Lr12YQ.js +3 -0
  99. package/dist/node-host/assets/git-commit-BU1ZeqU-.js +6 -0
  100. package/dist/node-host/assets/git-rebase-BBwLmuqc.js +6 -0
  101. package/dist/node-host/assets/github-dark-BXgKmOw_.js +4 -0
  102. package/dist/node-host/assets/github-dark-default-DzJQXksd.js +4 -0
  103. package/dist/node-host/assets/github-dark-dimmed-B1pEmGtS.js +4 -0
  104. package/dist/node-host/assets/github-dark-high-contrast-BkHIa6jc.js +4 -0
  105. package/dist/node-host/assets/github-light-BKRlUbaB.js +4 -0
  106. package/dist/node-host/assets/github-light-default-BU3eYkzs.js +4 -0
  107. package/dist/node-host/assets/github-light-high-contrast-B3MumDcI.js +4 -0
  108. package/dist/node-host/assets/gleam-CTllUdfC.js +3 -0
  109. package/dist/node-host/assets/glimmer-js-Cr4KUylv.js +15 -0
  110. package/dist/node-host/assets/glimmer-ts-W_3bhuUU.js +15 -0
  111. package/dist/node-host/assets/glsl-CV8OarLe.js +8 -0
  112. package/dist/node-host/assets/gn-CpIn4Dcw.js +3 -0
  113. package/dist/node-host/assets/gnuplot-DgWvVXI8.js +3 -0
  114. package/dist/node-host/assets/go-aXtTCanQ.js +3 -0
  115. package/dist/node-host/assets/graphql-BzQEBhBd.js +17 -0
  116. package/dist/node-host/assets/groovy-Bw2Ku4bU.js +3 -0
  117. package/dist/node-host/assets/gruvbox-dark-hard-B0ecF443.js +4 -0
  118. package/dist/node-host/assets/gruvbox-dark-medium-BQnpC39k.js +4 -0
  119. package/dist/node-host/assets/gruvbox-dark-soft-DFZUgESr.js +4 -0
  120. package/dist/node-host/assets/gruvbox-light-hard-CuB_LOJr.js +4 -0
  121. package/dist/node-host/assets/gruvbox-light-medium-idDHgxjs.js +4 -0
  122. package/dist/node-host/assets/gruvbox-light-soft-CfWH49MY.js +4 -0
  123. package/dist/node-host/assets/hack-DhYZEMlu.js +11 -0
  124. package/dist/node-host/assets/haml-CRid5kR3.js +13 -0
  125. package/dist/node-host/assets/handlebars-Sm6kCGmL.js +15 -0
  126. package/dist/node-host/assets/haskell-CfGsE1Cf.js +3 -0
  127. package/dist/node-host/assets/haxe-QaqXWmIB.js +3 -0
  128. package/dist/node-host/assets/hcl-DHg_Osgo.js +3 -0
  129. package/dist/node-host/assets/hjson-Cuaphql8.js +3 -0
  130. package/dist/node-host/assets/hlsl-BJLUNfne.js +3 -0
  131. package/dist/node-host/assets/horizon-bright-Ba6M8QSV.js +4 -0
  132. package/dist/node-host/assets/horizon-h92C5OlZ.js +4 -0
  133. package/dist/node-host/assets/houston-EQnPegWq.js +4 -0
  134. package/dist/node-host/assets/html-derivative-Cm8plTfc.js +6 -0
  135. package/dist/node-host/assets/html-j1vihRKA.js +13 -0
  136. package/dist/node-host/assets/http-BZNc4yhO.js +15 -0
  137. package/dist/node-host/assets/hurl-O8ckTHPH.js +13 -0
  138. package/dist/node-host/assets/hxml-9hvSHCPQ.js +6 -0
  139. package/dist/node-host/assets/hy-CJKxDzbh.js +3 -0
  140. package/dist/node-host/assets/imba-96qKoJCa.js +3 -0
  141. package/dist/node-host/assets/ini-C24lZfdM.js +3 -0
  142. package/dist/node-host/assets/java-CFxSaKyu.js +6 -0
  143. package/dist/node-host/assets/javascript-D44WgUBn.js +6 -0
  144. package/dist/node-host/assets/jinja-BkVH86If.js +10 -0
  145. package/dist/node-host/assets/jison-DOInKsmT.js +6 -0
  146. package/dist/node-host/assets/jiti-CjBwKumX.js +5461 -0
  147. package/dist/node-host/assets/json-CnGl9idC.js +6 -0
  148. package/dist/node-host/assets/json5-xULSgPQI.js +3 -0
  149. package/dist/node-host/assets/jsonc-Cu6rqcrJ.js +3 -0
  150. package/dist/node-host/assets/jsonl-Bp0qqr10.js +3 -0
  151. package/dist/node-host/assets/jsonnet-BafgE6cE.js +3 -0
  152. package/dist/node-host/assets/jssm-Dx5A5sBR.js +3 -0
  153. package/dist/node-host/assets/jsx-Dv6553c2.js +6 -0
  154. package/dist/node-host/assets/julia-CnoY-eZu.js +17 -0
  155. package/dist/node-host/assets/just-rCgUmVkD.js +19 -0
  156. package/dist/node-host/assets/kanagawa-dragon-DgDvwi3B.js +4 -0
  157. package/dist/node-host/assets/kanagawa-lotus-BgoQro20.js +4 -0
  158. package/dist/node-host/assets/kanagawa-wave-DgXEowHs.js +4 -0
  159. package/dist/node-host/assets/kdl-DkTyxPcj.js +3 -0
  160. package/dist/node-host/assets/kotlin-BHE8CoV9.js +3 -0
  161. package/dist/node-host/assets/kusto-6FdaWTzR.js +3 -0
  162. package/dist/node-host/assets/laserwave-IxBH_3yW.js +4 -0
  163. package/dist/node-host/assets/latex-DTvJHHg_.js +6 -0
  164. package/dist/node-host/assets/lean-BwOKScIt.js +3 -0
  165. package/dist/node-host/assets/less-BzbdhFUi.js +3 -0
  166. package/dist/node-host/assets/lib-BzDu3tbz.js +17484 -0
  167. package/dist/node-host/assets/light-plus-S8JBx6is.js +4 -0
  168. package/dist/node-host/assets/liquid-praBF2DO.js +15 -0
  169. package/dist/node-host/assets/llvm-ZNG_yAr9.js +3 -0
  170. package/dist/node-host/assets/log-Dad54gsi.js +3 -0
  171. package/dist/node-host/assets/logo-DTXV8SKw.js +3 -0
  172. package/dist/node-host/assets/lru-C0_oUet4.js +134 -0
  173. package/dist/node-host/assets/lua-CiYoSQn2.js +8 -0
  174. package/dist/node-host/assets/luau-DlImmvOM.js +3 -0
  175. package/dist/node-host/assets/main-D0ab5M4V.js +2353 -0
  176. package/dist/node-host/assets/make-B2ukzWs3.js +3 -0
  177. package/dist/node-host/assets/markdown-CkmxmzqY.js +3 -0
  178. package/dist/node-host/assets/marko-Din1W3ot.js +15 -0
  179. package/dist/node-host/assets/material-theme-D1f3HBq-.js +4 -0
  180. package/dist/node-host/assets/material-theme-darker-ExzYhT0j.js +4 -0
  181. package/dist/node-host/assets/material-theme-lighter-D6gH9nnA.js +4 -0
  182. package/dist/node-host/assets/material-theme-ocean-D1w5BPAa.js +4 -0
  183. package/dist/node-host/assets/material-theme-palenight-CkqWmVpF.js +4 -0
  184. package/dist/node-host/assets/matlab-CfRK6IMC.js +3 -0
  185. package/dist/node-host/assets/mdc-DrUS0kOP.js +13 -0
  186. package/dist/node-host/assets/mdx-BK9ZufRc.js +3 -0
  187. package/dist/node-host/assets/mermaid-ItFqCK9s.js +3 -0
  188. package/dist/node-host/assets/min-dark-npsJH98j.js +4 -0
  189. package/dist/node-host/assets/min-light-DUk1XsVt.js +4 -0
  190. package/dist/node-host/assets/mipsasm-EL46Y4Xg.js +3 -0
  191. package/dist/node-host/assets/mojo-CsUSYzsu.js +3 -0
  192. package/dist/node-host/assets/monokai-CfFRWUNl.js +4 -0
  193. package/dist/node-host/assets/moonbit-BuY4M1CG.js +3 -0
  194. package/dist/node-host/assets/move-B-o8dn0N.js +3 -0
  195. package/dist/node-host/assets/narrat-D9rrR0H5.js +3 -0
  196. package/dist/node-host/assets/nextflow-ByPelpfh.js +6 -0
  197. package/dist/node-host/assets/nextflow-groovy-kiH-pxwE.js +3 -0
  198. package/dist/node-host/assets/nginx-CqxQjy7t.js +6 -0
  199. package/dist/node-host/assets/night-owl-BS7V-av_.js +4 -0
  200. package/dist/node-host/assets/night-owl-light-CZtRawYE.js +4 -0
  201. package/dist/node-host/assets/nim-BJfyW_Eo.js +21 -0
  202. package/dist/node-host/assets/nix-NHxlegoK.js +7 -0
  203. package/dist/node-host/assets/node-CaLaDAar.js +783 -0
  204. package/dist/node-host/assets/nord-BFh54Nqz.js +4 -0
  205. package/dist/node-host/assets/nsis-C3tUpeAt.js +3 -0
  206. package/dist/node-host/assets/nushell-Dx3871HW.js +3 -0
  207. package/dist/node-host/assets/objective-c-6dJF64Dt.js +3 -0
  208. package/dist/node-host/assets/objective-cpp-_ooRQ-PY.js +3 -0
  209. package/dist/node-host/assets/ocaml-vm1Y8An6.js +3 -0
  210. package/dist/node-host/assets/odin-vjOJSyfV.js +3 -0
  211. package/dist/node-host/assets/one-dark-pro-zDovGosB.js +4 -0
  212. package/dist/node-host/assets/one-light-gmHK3iK6.js +4 -0
  213. package/dist/node-host/assets/openscad-jn-f1Ocl.js +3 -0
  214. package/dist/node-host/assets/org-kbyZnkNe.js +101 -0
  215. package/dist/node-host/assets/pages-0aWH7z1j.js +578 -0
  216. package/dist/node-host/assets/pascal-D8-gNZ2I.js +3 -0
  217. package/dist/node-host/assets/perl-Ck21ZEIA.js +17 -0
  218. package/dist/node-host/assets/php-BI2xxNig.js +19 -0
  219. package/dist/node-host/assets/pierre-dark-DDWDl8g6.js +4 -0
  220. package/dist/node-host/assets/pierre-dark-protanopia-deuteranopia-C35UMuvO.js +4 -0
  221. package/dist/node-host/assets/pierre-dark-soft-Bb-4-biO.js +4 -0
  222. package/dist/node-host/assets/pierre-dark-tritanopia-CIJISGWF.js +4 -0
  223. package/dist/node-host/assets/pierre-dark-vibrant-8oZzB3gw.js +4 -0
  224. package/dist/node-host/assets/pierre-light-CWhW4NrH.js +4 -0
  225. package/dist/node-host/assets/pierre-light-protanopia-deuteranopia-u0U7N4Hn.js +4 -0
  226. package/dist/node-host/assets/pierre-light-soft-BCU6fuhO.js +4 -0
  227. package/dist/node-host/assets/pierre-light-tritanopia-3c8Yx3co.js +4 -0
  228. package/dist/node-host/assets/pierre-light-vibrant-Cwh332o_.js +4 -0
  229. package/dist/node-host/assets/pkl-C7lWzykI.js +3 -0
  230. package/dist/node-host/assets/plastic-DtdaCjub.js +4 -0
  231. package/dist/node-host/assets/plsql-DCNXypXO.js +3 -0
  232. package/dist/node-host/assets/po-IjpR1yHO.js +3 -0
  233. package/dist/node-host/assets/poimandres-Hdda6aHK.js +4 -0
  234. package/dist/node-host/assets/polar-4pN1JrJ8.js +3 -0
  235. package/dist/node-host/assets/postcss-0FnF2XR3.js +5833 -0
  236. package/dist/node-host/assets/postcss-D1gKt4z_.js +3 -0
  237. package/dist/node-host/assets/postcss-import-D5DErX4i.js +445 -0
  238. package/dist/node-host/assets/powerquery-SrvS_NBM.js +3 -0
  239. package/dist/node-host/assets/powershell-DvPkO2jl.js +3 -0
  240. package/dist/node-host/assets/prisma-4G_BaF_i.js +3 -0
  241. package/dist/node-host/assets/project-branches-1zs_0J_y.js +600 -0
  242. package/dist/node-host/assets/project-thread-links-LpUAGcDr.js +241 -0
  243. package/dist/node-host/assets/prolog-Bacii3Ue.js +3 -0
  244. package/dist/node-host/assets/prompt-CH6TK0bC-ZEO69UbU.js +885 -0
  245. package/dist/node-host/assets/proto-ClkOcAW0.js +3 -0
  246. package/dist/node-host/assets/pug-Du-dOfS3.js +13 -0
  247. package/dist/node-host/assets/puppet-CQlPp728.js +3 -0
  248. package/dist/node-host/assets/purescript-CLpaGMbw.js +3 -0
  249. package/dist/node-host/assets/python-BjnOIxPe.js +3 -0
  250. package/dist/node-host/assets/qml-BcFatdsQ.js +6 -0
  251. package/dist/node-host/assets/qmldir-Dz8ttxjf.js +3 -0
  252. package/dist/node-host/assets/qss-Iltvkn1C.js +3 -0
  253. package/dist/node-host/assets/r-DvDYa_8Q.js +6 -0
  254. package/dist/node-host/assets/racket-DRv7j6nv.js +3 -0
  255. package/dist/node-host/assets/raku-DI9yGpeW.js +3 -0
  256. package/dist/node-host/assets/razor-vGjCiVYw.js +11 -0
  257. package/dist/node-host/assets/rbs-D_4FkXDS.js +3 -0
  258. package/dist/node-host/assets/red-VqZFRByZ.js +4 -0
  259. package/dist/node-host/assets/reg-DpJf71oO.js +3 -0
  260. package/dist/node-host/assets/regexp-CE_3vZDh.js +6 -0
  261. package/dist/node-host/assets/rel-BT0iIKKw.js +3 -0
  262. package/dist/node-host/assets/riscv-CvzikGLb.js +3 -0
  263. package/dist/node-host/assets/rolldown-runtime-DktXgydw.js +35 -0
  264. package/dist/node-host/assets/ron-DzAo6FTJ.js +3 -0
  265. package/dist/node-host/assets/rose-pine-DiTkEueR.js +4 -0
  266. package/dist/node-host/assets/rose-pine-dawn-tfo5y2X0.js +4 -0
  267. package/dist/node-host/assets/rose-pine-moon-C0gON9XN.js +4 -0
  268. package/dist/node-host/assets/rosmsg-CyYq6XdT.js +3 -0
  269. package/dist/node-host/assets/rst-BqrTc4by.js +23 -0
  270. package/dist/node-host/assets/ruby-DLXfYriJ.js +31 -0
  271. package/dist/node-host/assets/rust-CaJo2YYS.js +3 -0
  272. package/dist/node-host/assets/sas-DJAJgMrq.js +6 -0
  273. package/dist/node-host/assets/sass-DI2zJwx7.js +3 -0
  274. package/dist/node-host/assets/scala-pMGZAs9j.js +3 -0
  275. package/dist/node-host/assets/scheme-D3A2QBtM.js +3 -0
  276. package/dist/node-host/assets/screen-DQoTOWYR.js +494 -0
  277. package/dist/node-host/assets/scss-n4nz8dJI.js +8 -0
  278. package/dist/node-host/assets/sdbl-CdQg5gu1.js +3 -0
  279. package/dist/node-host/assets/shaderlab-5KHT7nQ3.js +6 -0
  280. package/dist/node-host/assets/shellscript-BfuVOb-a.js +6 -0
  281. package/dist/node-host/assets/shellsession-hT9eQUDu.js +6 -0
  282. package/dist/node-host/assets/slack-dark-v8P_eXr6.js +4 -0
  283. package/dist/node-host/assets/slack-ochin-D8SyEdKz.js +4 -0
  284. package/dist/node-host/assets/smalltalk-D4IpSogw.js +3 -0
  285. package/dist/node-host/assets/smithy-BRyN83Yn.js +3 -0
  286. package/dist/node-host/assets/snazzy-light-BtkQkvXb.js +4 -0
  287. package/dist/node-host/assets/solarized-dark-B8nAdMfM.js +4 -0
  288. package/dist/node-host/assets/solarized-light-B-3F1INj.js +4 -0
  289. package/dist/node-host/assets/solidity-DPVu0wkJ.js +3 -0
  290. package/dist/node-host/assets/soy-Cbnz3v4z.js +6 -0
  291. package/dist/node-host/assets/sparql-cTZV4yOr.js +6 -0
  292. package/dist/node-host/assets/splunk-xP_ZjUH2.js +3 -0
  293. package/dist/node-host/assets/sql-DEvbt_rU.js +6 -0
  294. package/dist/node-host/assets/ssh-config-DXFPABYp.js +3 -0
  295. package/dist/node-host/assets/stata-0wnAnxv2.js +6 -0
  296. package/dist/node-host/assets/stylus-ZZDn1yUO.js +3 -0
  297. package/dist/node-host/assets/surrealql-70--UPGf.js +6 -0
  298. package/dist/node-host/assets/svelte-COI8WXVC.js +15 -0
  299. package/dist/node-host/assets/swift-DWmMn3lF.js +3 -0
  300. package/dist/node-host/assets/synthwave-84-DcsXCF_c.js +4 -0
  301. package/dist/node-host/assets/system-verilog-DW0KSMVO.js +3 -0
  302. package/dist/node-host/assets/systemd-DFbUltlW.js +3 -0
  303. package/dist/node-host/assets/talonscript-CLxaFu-b.js +3 -0
  304. package/dist/node-host/assets/tasks-C0WqnM7Q.js +644 -0
  305. package/dist/node-host/assets/tasks-request-Bwky5ORf.js +114 -0
  306. package/dist/node-host/assets/tasl-_AAbKiWM.js +3 -0
  307. package/dist/node-host/assets/tcl-DLe0Tsfm.js +3 -0
  308. package/dist/node-host/assets/templ-Bd0jzThC.js +13 -0
  309. package/dist/node-host/assets/terraform-v0KN9QQh.js +3 -0
  310. package/dist/node-host/assets/tex-C1Wtngrr.js +8 -0
  311. package/dist/node-host/assets/tokyo-night-Bvpqduda.js +4 -0
  312. package/dist/node-host/assets/toml-B7BHZIqX.js +3 -0
  313. package/dist/node-host/assets/ts-tags-BSGaPI18.js +59 -0
  314. package/dist/node-host/assets/tsv-BorGVq2Z.js +3 -0
  315. package/dist/node-host/assets/tsx-BxnCmyTp.js +6 -0
  316. package/dist/node-host/assets/turtle-BEG5g_eT.js +3 -0
  317. package/dist/node-host/assets/twig-BJG2k0MA.js +19 -0
  318. package/dist/node-host/assets/typescript-Cr5OHcug.js +6 -0
  319. package/dist/node-host/assets/typespec--jp0a8Qj.js +3 -0
  320. package/dist/node-host/assets/typst-BAvHLpwp.js +121 -0
  321. package/dist/node-host/assets/v-DBTjbKF9.js +3 -0
  322. package/dist/node-host/assets/vala-BnnASFsX.js +3 -0
  323. package/dist/node-host/assets/vb-eGVhCUXq.js +3 -0
  324. package/dist/node-host/assets/verilog-Ma7yBE30.js +3 -0
  325. package/dist/node-host/assets/vesper-CfxmMOy3.js +4 -0
  326. package/dist/node-host/assets/vhdl-Co2XFKY7.js +3 -0
  327. package/dist/node-host/assets/viml-BReTXeDA.js +3 -0
  328. package/dist/node-host/assets/vitesse-black-D9-_1mjV.js +4 -0
  329. package/dist/node-host/assets/vitesse-dark-DlSMLlr9.js +4 -0
  330. package/dist/node-host/assets/vitesse-light-KRWWWztf.js +4 -0
  331. package/dist/node-host/assets/vue-C3fWU2fi.js +31 -0
  332. package/dist/node-host/assets/vue-html-DxOM9wex.js +6 -0
  333. package/dist/node-host/assets/vue-vine-OKzSGBlX.js +19 -0
  334. package/dist/node-host/assets/vyper-C6KGx0YU.js +3 -0
  335. package/dist/node-host/assets/wasm-CvHAs-FG.js +7 -0
  336. package/dist/node-host/assets/wasm-gNIKwGlD.js +3 -0
  337. package/dist/node-host/assets/wenyan-BeysbCrW.js +3 -0
  338. package/dist/node-host/assets/wgsl-1s5rV5xS.js +3 -0
  339. package/dist/node-host/assets/wikitext-wjhjmmzD.js +3 -0
  340. package/dist/node-host/assets/wit-CeQiCbz_.js +3 -0
  341. package/dist/node-host/assets/wolfram-CcZPfnLd.js +3 -0
  342. package/dist/node-host/assets/worker-factory-C80HGMWf.js +14 -0
  343. package/dist/node-host/assets/xml-BsBjJrlv.js +8 -0
  344. package/dist/node-host/assets/xsl-BYwWzvT_.js +6 -0
  345. package/dist/node-host/assets/yaml-BRgACZP0.js +6 -0
  346. package/dist/node-host/assets/zenscript-BoijL7rl.js +3 -0
  347. package/dist/node-host/assets/zig-lTQvxhLD.js +3 -0
  348. package/dist/node-host/node-host.js +41511 -0
  349. package/dist/server/assets/{branch-previews-C_TVt_HQ.js → branch-previews-BryeA_kA.js} +112 -3
  350. package/dist/server/assets/{listener-BVbFsnmX.js → listener-oKd5ETMj.js} +3 -3
  351. package/dist/server/assets/{server-DUBjmaBw.js → server-DN5uwE4k.js} +1 -1
  352. package/dist/server/assets/{services-Og5rcF50.js → services-Ber8opFE.js} +2 -2
  353. package/dist/server/capture.js +1 -1
  354. package/dist/server/migration.js +3 -3
  355. package/dist/server/runtime.js +4 -4
  356. package/dist/server/server.js +3 -3
  357. package/dist/types/assets/{config-BzzRLcL-.d.ts → config-Cuauq8xY.d.ts} +24 -1
  358. package/dist/types/assets/contract-BsCQbbxD.d.ts +111 -0
  359. package/dist/types/companion.d.ts +1 -1
  360. package/dist/types/hosted.d.ts +3 -99
  361. package/dist/types/index.d.ts +1 -1
  362. package/dist/types/node-host.d.ts +46 -0
  363. package/dist/types/screen.d.ts +1 -1
  364. package/package.json +6 -2
  365. package/dist/client/assets/workspace-9KxFcfyV.js +0 -2
@@ -0,0 +1,3771 @@
1
+ import { i as __toESM } from "./rolldown-runtime-DktXgydw.js";
2
+ import { $ as FileSearchPanel, $n as resolveLanguage, A as parseEditorPreferences, An as reviewConflictIdentity, At as defaultReviewView, Bn as markWorkerErrorReported, Bt as fileHref, D as ReviewActionDialog, E as FileConflictPanel, F as FileWorkspace, Fn as areDiffTargetsEqual, Ft as reviewComparison, Gn as areThemesEqual, Gt as fileRoutes, Hn as renderFileWithHighlighter, Ht as readFileLocation, I as defaultFileDestination, In as WorkerPoolTaskCanceledError, It as reviewContextKey, Jn as validateResolvedThemeName, Kn as getSharedHighlighter, L as fileActionIntent, Ln as WorkerPoolTerminatedError, Lt as reviewHref, M as FileMutationDialog, Mn as renderDiffWithHighlighter, Mt as parseReviewSession, N as FileReviewPanel, Nn as areDiffRenderOptionsEqual, Nt as readReviewLocation, O as FileEditorPanel, On as createCodeAnchor, P as codeThemes, Pn as areFilesEqual, Pt as rememberReviewPosition, Qn as getResolvedOrResolveLanguage, R as mutationFileView, Rn as WorkerPoolWorkerError, Rt as closeFileTab, St as isCodeConversation, Tt as readConversationLocation, Un as areFileRenderOptionsEqual, Ut as reconcileFileView, Vn as isFilePlainText, Vt as parseFileView, Wn as hasResolvedThemes, Wt as rememberFile, Xn as themeResolver, Yn as attachResolvedThemes, Zn as getThemes, _r as parseFileSearchResult, ar as parseFileSaveRequest, br as parseFileIndex, bt as conversationHref, cn as toast, cr as parseMutationPlan, dn as Text, dr as parseReviewDiff, er as areLanguagesAttached, f as storeDocument, fr as parseReviewHistory, gr as parseFileSearchQuery, hr as parseReviewSource, ir as parseReviewActionResult, j as FileRecoveryDialog, jn as isDiffPlainText, k as defaultEditorPreferences, kn as parseFileUpdate, kt as defaultReviewPosition, l as projectStore, ln as readPreference, lr as parseMutationResult, m as storedFiles, mr as parseReviewSnapshot, nr as parseReviewActionPlan, or as parseFileSaveResult, p as storedDocument, pr as parseReviewRepository, qn as prepareThemeResolution, rr as parseReviewActionRecoveries, s as applyFileUpdate, sr as maxFileSaveBytes, t as commentsUnavailable, tr as ResolvedLanguages, tt as ConversationSidebar, u as saveFiles, un as writePreference, ur as parseRecoveries, v as projectLive, vr as parseFileChange, vt as codeCommentCounts, wt as locateCodeConversations, xr as parseFilePath, y as routes, yr as parseFileDocument, z as remainingMutationIntent, zn as isHandledWorkerPoolError, zt as defaultFileView } from "./comment-data-nUlI0szN.js";
3
+ import { B as DEFAULT_THEMES, C as getFiletypeFromFileName, S as getCustomExtensionsVersion, x as getCustomExtensionsMap } from "./cloneFileDiffMetadata-C1BcNpxN.js";
4
+ import { n as createProjectComments, t as defineScreen } from "./screen-DQoTOWYR.js";
5
+ import { t as require_lru } from "./lru-C0_oUet4.js";
6
+ import { t as ProjectThreadLinks } from "./project-thread-links-LpUAGcDr.js";
7
+ import { ref } from "remix/component";
8
+ import { jsx, jsxs } from "remix/component/jsx-runtime";
9
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.5.2_@shikijs+themes@4.5.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@pierre/diffs/dist/highlighter/languages/getResolvedLanguages.js
10
+ function getResolvedLanguages(languages) {
11
+ const resolvedLanguages = [];
12
+ for (const language of languages) {
13
+ const resolvedLanguage = ResolvedLanguages.get(language);
14
+ if (resolvedLanguage == null) throw new Error(`getResolvedLanguages: ${language} is not resolved. Please resolve languages before calling getResolvedLanguages`);
15
+ resolvedLanguages.push(resolvedLanguage);
16
+ }
17
+ return resolvedLanguages;
18
+ }
19
+ //#endregion
20
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.5.2_@shikijs+themes@4.5.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@pierre/diffs/dist/highlighter/languages/hasResolvedLanguages.js
21
+ function hasResolvedLanguages(languages) {
22
+ for (const language of Array.isArray(languages) ? languages : [languages]) if (!ResolvedLanguages.has(language)) return false;
23
+ return true;
24
+ }
25
+ //#endregion
26
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.5.2_@shikijs+themes@4.5.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@pierre/diffs/dist/highlighter/languages/resolveLanguages.js
27
+ async function resolveLanguages(languages) {
28
+ const resolvedLanguages = [];
29
+ const languagesToResolve = [];
30
+ for (const language of languages) {
31
+ if (language === "text" || language === "ansi") continue;
32
+ const maybeResolvedLanguage = getResolvedOrResolveLanguage(language) ?? resolveLanguage(language);
33
+ if ("then" in maybeResolvedLanguage) languagesToResolve.push(maybeResolvedLanguage);
34
+ else resolvedLanguages.push(maybeResolvedLanguage);
35
+ }
36
+ if (languagesToResolve.length > 0) await Promise.all(languagesToResolve).then((_resolvedLanguages) => {
37
+ for (const resolvedLanguage of _resolvedLanguages) {
38
+ if (resolvedLanguage == null) throw new Error("resolvedLanguages: unable to resolve language");
39
+ resolvedLanguages.push(resolvedLanguage);
40
+ }
41
+ });
42
+ return resolvedLanguages;
43
+ }
44
+ //#endregion
45
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.5.2_@shikijs+themes@4.5.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@pierre/diffs/dist/highlighter/themes/getResolvedThemes.js
46
+ function getResolvedThemes(themeNames) {
47
+ return themeResolver.getResolvedThemes(themeNames);
48
+ }
49
+ //#endregion
50
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.5.2_@shikijs+themes@4.5.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@pierre/diffs/dist/highlighter/themes/resolveThemes.js
51
+ async function resolveThemes(themes) {
52
+ for (const themeName of themes) prepareThemeResolution(themeName);
53
+ const resolvedThemes = await themeResolver.resolveThemes(themes);
54
+ for (let i = 0; i < themes.length; i++) validateResolvedThemeName(themes[i], resolvedThemes[i]);
55
+ return resolvedThemes;
56
+ }
57
+ //#endregion
58
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.5.2_@shikijs+themes@4.5.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@pierre/diffs/dist/worker/WorkerPoolManager.js
59
+ var import_lru = /* @__PURE__ */ __toESM(require_lru(), 1);
60
+ var IGNORE_RESPONSE = Symbol("IGNORE_RESPONSE");
61
+ var DEFAULT_WORKER_INITIALIZATION_TIMEOUT = 1e4;
62
+ var WorkerPoolManager = class {
63
+ options;
64
+ highlighter;
65
+ preferredHighlighter;
66
+ renderOptions;
67
+ renderOptionsRequestVersion = 0;
68
+ renderOptionsVersion = 0;
69
+ initialized = false;
70
+ workers = [];
71
+ queuedTasks = [];
72
+ queuedTaskByInstance = /* @__PURE__ */ new Map();
73
+ taskByHighlightKey = /* @__PURE__ */ new Map();
74
+ activeTaskById = /* @__PURE__ */ new Map();
75
+ activeRequestByInstance = /* @__PURE__ */ new Map();
76
+ nextRequestId = 0;
77
+ themeSubscribers = /* @__PURE__ */ new Set();
78
+ workersFailed = false;
79
+ statSubscribers = /* @__PURE__ */ new Set();
80
+ fileCache;
81
+ diffCache;
82
+ _queuedBroadcast;
83
+ lifecycleGeneration = 0;
84
+ constructor(options, { langs, theme = DEFAULT_THEMES, useTokenTransformer = false, lineDiffType = "word-alt", maxLineDiffLength = 1e3, tokenizeMaxLineLength = 1e3, preferredHighlighter = "shiki-js" }) {
85
+ this.options = options;
86
+ this.preferredHighlighter = preferredHighlighter;
87
+ this.renderOptions = {
88
+ theme,
89
+ useTokenTransformer,
90
+ lineDiffType,
91
+ maxLineDiffLength,
92
+ tokenizeMaxLineLength
93
+ };
94
+ this.fileCache = new import_lru.default.LRUMap(options.totalASTLRUCacheSize ?? 100);
95
+ this.diffCache = new import_lru.default.LRUMap(options.totalASTLRUCacheSize ?? 100);
96
+ this.queueInitialization(langs);
97
+ }
98
+ isWorkingPool() {
99
+ return !this.workersFailed;
100
+ }
101
+ getFileResultCache(file) {
102
+ return file.cacheKey != null ? this.fileCache.get(file.cacheKey) : void 0;
103
+ }
104
+ getDiffResultCache(diff) {
105
+ return diff.cacheKey != null ? this.diffCache.get(diff.cacheKey) : void 0;
106
+ }
107
+ inspectCaches() {
108
+ const { fileCache, diffCache } = this;
109
+ return {
110
+ fileCache,
111
+ diffCache
112
+ };
113
+ }
114
+ evictFileFromCache(cacheKey) {
115
+ try {
116
+ return this.fileCache.delete(cacheKey) !== void 0;
117
+ } finally {
118
+ this.queueBroadcastStateChanges();
119
+ }
120
+ }
121
+ evictDiffFromCache(cacheKey) {
122
+ try {
123
+ return this.diffCache.delete(cacheKey) !== void 0;
124
+ } finally {
125
+ this.queueBroadcastStateChanges();
126
+ }
127
+ }
128
+ async setRenderOptions({ theme = this.renderOptions.theme ?? DEFAULT_THEMES, useTokenTransformer = this.renderOptions.useTokenTransformer ?? false, lineDiffType = this.renderOptions.lineDiffType ?? "word-alt", maxLineDiffLength = this.renderOptions.maxLineDiffLength ?? 1e3, tokenizeMaxLineLength = this.renderOptions.tokenizeMaxLineLength ?? 1e3 }) {
129
+ const { lifecycleGeneration } = this;
130
+ const renderOptionsRequestVersion = ++this.renderOptionsRequestVersion;
131
+ const isCurrentRequest = () => this.isCurrentLifecycle(lifecycleGeneration) && this.renderOptionsRequestVersion === renderOptionsRequestVersion;
132
+ try {
133
+ const newRenderOptions = {
134
+ theme,
135
+ useTokenTransformer,
136
+ lineDiffType,
137
+ maxLineDiffLength,
138
+ tokenizeMaxLineLength
139
+ };
140
+ if (!this.isInitialized()) await this.initialize();
141
+ if (!isCurrentRequest() || areDiffRenderOptionsEqual(newRenderOptions, this.renderOptions)) return;
142
+ const themeNames = getThemes(theme);
143
+ let resolvedThemes = [];
144
+ if (!areThemesEqual(newRenderOptions.theme, this.renderOptions.theme)) {
145
+ if (hasResolvedThemes(themeNames)) resolvedThemes = getResolvedThemes(themeNames);
146
+ else resolvedThemes = await resolveThemes(themeNames);
147
+ }
148
+ if (!isCurrentRequest()) return;
149
+ if (this.highlighter != null) attachResolvedThemes(resolvedThemes, this.highlighter);
150
+ else {
151
+ const highlighter = await getSharedHighlighter({
152
+ themes: themeNames,
153
+ langs: ["text"],
154
+ preferredHighlighter: this.preferredHighlighter
155
+ });
156
+ if (!isCurrentRequest()) return;
157
+ this.highlighter = highlighter;
158
+ }
159
+ const workerSetup = this.setRenderOptionsOnWorkers(newRenderOptions, resolvedThemes);
160
+ this.renderOptions = newRenderOptions;
161
+ this.renderOptionsVersion++;
162
+ this.diffCache.clear();
163
+ this.fileCache.clear();
164
+ this.invalidateRenderTasks();
165
+ for (const instance of this.themeSubscribers) instance.onThemeChange();
166
+ await workerSetup;
167
+ } catch (error) {
168
+ if (error instanceof WorkerPoolTerminatedError || !isCurrentRequest()) return;
169
+ throw error;
170
+ }
171
+ }
172
+ getPreferredHighlighter() {
173
+ return this.preferredHighlighter;
174
+ }
175
+ getFileRenderOptions() {
176
+ const { tokenizeMaxLineLength, theme, useTokenTransformer } = this.renderOptions;
177
+ return {
178
+ theme,
179
+ useTokenTransformer,
180
+ tokenizeMaxLineLength
181
+ };
182
+ }
183
+ getDiffRenderOptions() {
184
+ return { ...this.renderOptions };
185
+ }
186
+ async setRenderOptionsOnWorkers(renderOptions, resolvedThemes) {
187
+ if (this.workersFailed) return;
188
+ if (!this.isInitialized()) await this.initialize();
189
+ const taskPromises = [];
190
+ for (const managedWorker of this.workers) {
191
+ if (!managedWorker.initialized) {
192
+ console.log({ managedWorker });
193
+ throw new Error("setRenderOptionsOnWorkers: Somehow we have an uninitialized worker");
194
+ }
195
+ taskPromises.push(new Promise((resolve, reject) => {
196
+ const id = this.generateRequestId();
197
+ const task = {
198
+ type: "set-render-options",
199
+ id,
200
+ request: {
201
+ type: "set-render-options",
202
+ id,
203
+ renderOptions,
204
+ resolvedThemes
205
+ },
206
+ resolve,
207
+ reject,
208
+ requestStart: Date.now()
209
+ };
210
+ this.activeTaskById.set(id, task);
211
+ managedWorker.pendingSetupRequestId = id;
212
+ managedWorker.worker.postMessage(task.request);
213
+ }));
214
+ }
215
+ await Promise.all(taskPromises);
216
+ }
217
+ subscribeToThemeChanges(instance) {
218
+ this.themeSubscribers.add(instance);
219
+ this.queueBroadcastStateChanges();
220
+ return () => {
221
+ this.unsubscribeToThemeChanges(instance);
222
+ this.queueBroadcastStateChanges();
223
+ };
224
+ }
225
+ unsubscribeToThemeChanges(instance) {
226
+ this.themeSubscribers.delete(instance);
227
+ this.queueBroadcastStateChanges();
228
+ }
229
+ subscribeToStatChanges(callback) {
230
+ this.statSubscribers.add(callback);
231
+ callback(this.getStats());
232
+ return () => {
233
+ this.statSubscribers.delete(callback);
234
+ };
235
+ }
236
+ queueBroadcastStateChanges() {
237
+ if (this._queuedBroadcast != null) return;
238
+ this._queuedBroadcast = requestAnimationFrame(this._broadcastStateChanges);
239
+ }
240
+ _broadcastStateChanges = () => {
241
+ if (this._queuedBroadcast != null) {
242
+ cancelAnimationFrame(this._queuedBroadcast);
243
+ this._queuedBroadcast = void 0;
244
+ }
245
+ const stats = this.getStats();
246
+ for (const callback of this.statSubscribers) callback(stats);
247
+ };
248
+ cleanUpTasks(instance) {
249
+ this.detachInstanceFromQueuedTasks(instance);
250
+ const requestId = this.activeRequestByInstance.get(instance);
251
+ if (requestId != null) {
252
+ const task = this.activeTaskById.get(requestId);
253
+ if (isRenderTask(task)) {
254
+ this.detachInstanceFromRenderTask(task, instance);
255
+ if (!task.primeCache && task.instances.size === 0) this.removeActiveTask(task);
256
+ } else this.activeTaskById.delete(requestId);
257
+ }
258
+ this.activeRequestByInstance.delete(instance);
259
+ this.queueBroadcastStateChanges();
260
+ }
261
+ isInitialized() {
262
+ return this.initialized === true;
263
+ }
264
+ async initialize(languages = []) {
265
+ if (this.initialized === true) return;
266
+ else if (this.initialized === false) {
267
+ const { lifecycleGeneration } = this;
268
+ const initialization = new Promise((resolve, reject) => {
269
+ (async () => {
270
+ try {
271
+ const themes = getThemes(this.renderOptions.theme);
272
+ let resolvedThemes = [];
273
+ if (hasResolvedThemes(themes)) resolvedThemes = getResolvedThemes(themes);
274
+ else resolvedThemes = await resolveThemes(themes);
275
+ if (!this.isCurrentLifecycle(lifecycleGeneration)) {
276
+ resolve();
277
+ return;
278
+ }
279
+ let resolvedLanguages = [];
280
+ if (hasResolvedLanguages(languages)) resolvedLanguages = getResolvedLanguages(languages);
281
+ else resolvedLanguages = await resolveLanguages(languages);
282
+ if (!this.isCurrentLifecycle(lifecycleGeneration)) {
283
+ resolve();
284
+ return;
285
+ }
286
+ const [highlighter] = await Promise.all([getSharedHighlighter({
287
+ themes,
288
+ langs: ["text", ...languages],
289
+ preferredHighlighter: this.preferredHighlighter
290
+ }), this.initializeWorkers(resolvedThemes, resolvedLanguages)]);
291
+ if (!this.isCurrentLifecycle(lifecycleGeneration)) {
292
+ this.terminateWorkers();
293
+ resolve();
294
+ return;
295
+ }
296
+ this.highlighter = highlighter;
297
+ this.initialized = true;
298
+ this.diffCache.clear();
299
+ this.fileCache.clear();
300
+ this.drainQueue();
301
+ this.queueBroadcastStateChanges();
302
+ resolve();
303
+ } catch (e) {
304
+ if (e instanceof WorkerPoolTerminatedError || !this.isCurrentLifecycle(lifecycleGeneration)) {
305
+ resolve();
306
+ return;
307
+ }
308
+ this.initialized = false;
309
+ this.workersFailed = true;
310
+ this.cancelActiveWorkerTasks();
311
+ this.terminateWorkers();
312
+ this.activeTaskById.clear();
313
+ for (const task of this.queuedTasks) this.rejectRenderTaskCallbacks(task, normalizeWorkerError(e));
314
+ this.queueBroadcastStateChanges();
315
+ reject(e);
316
+ }
317
+ })();
318
+ });
319
+ this.initialized = initialization;
320
+ this.queueBroadcastStateChanges();
321
+ return initialization;
322
+ } else return this.initialized;
323
+ }
324
+ async initializeWorkers(resolvedThemes, resolvedLanguages) {
325
+ this.workersFailed = false;
326
+ const initPromises = [];
327
+ const customExtensionVersion = getCustomExtensionsVersion();
328
+ const customExtensionMap = customExtensionVersion > 0 ? getCustomExtensionsMap() : void 0;
329
+ if (this.workers.length > 0) this.terminateWorkers();
330
+ for (let i = 0; i < (this.options.poolSize ?? 8); i++) {
331
+ const worker = this.options.workerFactory();
332
+ const managedWorker = {
333
+ worker,
334
+ requestId: void 0,
335
+ pendingSetupRequestId: void 0,
336
+ initialized: false,
337
+ langs: /* @__PURE__ */ new Set(["text", ...resolvedLanguages.map(({ name }) => name)]),
338
+ customExtensionsVersion: 0
339
+ };
340
+ worker.addEventListener("message", (event) => {
341
+ this.handleWorkerMessage(managedWorker, event.data);
342
+ });
343
+ worker.addEventListener("error", (error) => this.handleWorkerError(managedWorker, error));
344
+ this.workers.push(managedWorker);
345
+ initPromises.push(new Promise((resolve, reject) => {
346
+ const id = this.generateRequestId();
347
+ const task = {
348
+ type: "initialize",
349
+ id,
350
+ request: {
351
+ type: "initialize",
352
+ id,
353
+ renderOptions: this.renderOptions,
354
+ preferredHighlighter: this.preferredHighlighter,
355
+ resolvedThemes,
356
+ resolvedLanguages,
357
+ customExtensionsVersion: customExtensionMap != null ? customExtensionVersion : void 0,
358
+ customExtensionMap
359
+ },
360
+ resolve() {
361
+ managedWorker.initialized = true;
362
+ resolve();
363
+ },
364
+ reject,
365
+ requestStart: Date.now()
366
+ };
367
+ managedWorker.pendingSetupRequestId = id;
368
+ this.activeTaskById.set(id, task);
369
+ this.executeTask(managedWorker, task);
370
+ }));
371
+ }
372
+ const timeoutMs = this.options.workerInitializationTimeout ?? DEFAULT_WORKER_INITIALIZATION_TIMEOUT;
373
+ let timeoutId;
374
+ try {
375
+ await Promise.race([Promise.all(initPromises), new Promise((_, reject) => {
376
+ timeoutId = setTimeout(() => {
377
+ reject(/* @__PURE__ */ new Error(`WorkerPoolManager: worker initialization timed out after ${timeoutMs}ms`));
378
+ }, timeoutMs);
379
+ })]);
380
+ } finally {
381
+ if (timeoutId != null) clearTimeout(timeoutId);
382
+ }
383
+ }
384
+ /**
385
+ * Pass worker errors to onWorkerError, or log them if no callback is set.
386
+ *
387
+ * A startup error rejects initialization so components can highlight on the
388
+ * main thread. Other requests wait for their own responses: worker.ts catches
389
+ * request errors, so this event may be unrelated to the current request.
390
+ * A worker that stops responding remains busy and receives no further work.
391
+ */
392
+ handleWorkerError(managedWorker, event) {
393
+ const { onWorkerError } = this.options;
394
+ if (onWorkerError != null) try {
395
+ onWorkerError(event, managedWorker.worker);
396
+ } catch (hookError) {
397
+ console.error("WorkerPoolManager: onWorkerError threw:", hookError);
398
+ }
399
+ else console.error("Worker error:", event, managedWorker);
400
+ const { pendingSetupRequestId } = managedWorker;
401
+ if (pendingSetupRequestId == null) return;
402
+ const task = this.activeTaskById.get(pendingSetupRequestId);
403
+ if (task?.type !== "initialize") return;
404
+ const error = errorFromWorkerErrorEvent(event);
405
+ if (onWorkerError != null) markWorkerErrorReported(error);
406
+ task.reject(error);
407
+ this.cleanWorkerAndTask(managedWorker, task);
408
+ }
409
+ drainQueue = () => {
410
+ this._queuedDrain = void 0;
411
+ if (this.initialized !== true || this.queuedTasks.length === 0) return;
412
+ for (let i = 0; i < this.queuedTasks.length;) {
413
+ const task = this.queuedTasks[i];
414
+ if (this.hasActiveRequest(task)) {
415
+ i++;
416
+ continue;
417
+ }
418
+ const langs = getLangsFromTask(task);
419
+ const availableWorker = this.getAvailableWorker(langs);
420
+ if (availableWorker == null) break;
421
+ this.queuedTasks.splice(i, 1);
422
+ this.assignWorkerToTask(task, availableWorker);
423
+ this.resolveLanguagesAndExecuteTask(availableWorker, task, langs);
424
+ }
425
+ this.queueBroadcastStateChanges();
426
+ };
427
+ highlightFileAST(instance, file) {
428
+ const cachedResult = this.getFileResultCache(file);
429
+ if (isFilePlainText(file) || cachedResult != null && areFileRenderOptionsEqual(cachedResult.options, this.getFileRenderOptions())) return;
430
+ if (!this.hasMatchingFileInstanceTask(instance, file)) this.submitTask(instance, {
431
+ type: "file",
432
+ file
433
+ });
434
+ }
435
+ primeFileHighlightCache(file) {
436
+ if (!this.isWorkingPool()) return Promise.reject(/* @__PURE__ */ new Error("WorkerPoolManager.primeFileHighlightCache: worker pool is not working"));
437
+ const task = this.getOrCreateFileHighlightCacheTask(file);
438
+ return task != null ? this.createRenderTaskCallbacks(task) : Promise.resolve();
439
+ }
440
+ getOrCreateFileHighlightCacheTask(file) {
441
+ if (file.cacheKey == null) {
442
+ console.warn(`WorkerPoolManager.primeFileHighlightCache: priming highlight cache requires file.cacheKey; skipping "${file.name}".`);
443
+ return;
444
+ }
445
+ const cachedResult = this.getFileResultCache(file);
446
+ const highlightKey = this.getFileHighlightKey(file);
447
+ if (highlightKey == null || isFilePlainText(file) || cachedResult != null && areFileRenderOptionsEqual(cachedResult.options, this.getFileRenderOptions())) return;
448
+ const existingTask = this.getTaskByHighlightKey(highlightKey);
449
+ if (existingTask != null) {
450
+ existingTask.primeCache = true;
451
+ return existingTask.type === "file" ? existingTask : void 0;
452
+ } else {
453
+ const task = this.submitCacheTask({
454
+ type: "file",
455
+ file
456
+ }, highlightKey);
457
+ return task.type === "file" ? task : void 0;
458
+ }
459
+ }
460
+ getPlainFileAST(file, startingLine, totalLines, lines) {
461
+ if (this.highlighter == null) {
462
+ this.queueInitialization();
463
+ return;
464
+ }
465
+ return renderFileWithHighlighter(file, this.highlighter, this.renderOptions, {
466
+ forcePlainText: true,
467
+ startingLine,
468
+ totalLines,
469
+ lines
470
+ });
471
+ }
472
+ highlightDiffAST(instance, diff) {
473
+ const cachedResult = this.getDiffResultCache(diff);
474
+ if (isDiffPlainText(diff) || cachedResult != null && areDiffRenderOptionsEqual(cachedResult.options, this.getDiffRenderOptions())) return;
475
+ if (!this.hasMatchingDiffInstanceTask(instance, diff)) this.submitTask(instance, {
476
+ type: "diff",
477
+ diff
478
+ });
479
+ }
480
+ primeDiffHighlightCache(diff) {
481
+ if (!this.isWorkingPool()) return Promise.reject(/* @__PURE__ */ new Error("WorkerPoolManager.primeDiffHighlightCache: worker pool is not working"));
482
+ const task = this.getOrCreateDiffHighlightCacheTask(diff);
483
+ return task != null ? this.createRenderTaskCallbacks(task) : Promise.resolve();
484
+ }
485
+ getOrCreateDiffHighlightCacheTask(diff) {
486
+ if (diff.cacheKey == null) {
487
+ console.warn(`WorkerPoolManager.primeDiffHighlightCache: priming highlight cache requires diff.cacheKey; skipping "${diff.prevName ?? diff.name}" -> "${diff.name}".`);
488
+ return;
489
+ }
490
+ const cachedResult = this.getDiffResultCache(diff);
491
+ const highlightKey = this.getDiffHighlightKey(diff);
492
+ if (highlightKey == null || isDiffPlainText(diff) || cachedResult != null && areDiffRenderOptionsEqual(cachedResult.options, this.getDiffRenderOptions())) return;
493
+ const existingTask = this.getTaskByHighlightKey(highlightKey);
494
+ if (existingTask != null) {
495
+ existingTask.primeCache = true;
496
+ return existingTask.type === "diff" ? existingTask : void 0;
497
+ } else {
498
+ const task = this.submitCacheTask({
499
+ type: "diff",
500
+ diff
501
+ }, highlightKey);
502
+ return task.type === "diff" ? task : void 0;
503
+ }
504
+ }
505
+ getPlainDiffAST(diff, startingLine, totalLines, expandedHunks, collapsedContextThreshold) {
506
+ return this.highlighter != null ? renderDiffWithHighlighter(diff, this.highlighter, this.renderOptions, {
507
+ forcePlainText: true,
508
+ startingLine,
509
+ totalLines,
510
+ expandedHunks,
511
+ collapsedContextThreshold
512
+ }) : void 0;
513
+ }
514
+ terminate() {
515
+ this.lifecycleGeneration++;
516
+ this.cancelActiveWorkerTasks();
517
+ this.terminateWorkers();
518
+ const error = new WorkerPoolTerminatedError();
519
+ for (const task of this.queuedTasks) this.rejectRenderTaskCallbacks(task, error);
520
+ this.fileCache.clear();
521
+ this.diffCache.clear();
522
+ this.activeRequestByInstance.clear();
523
+ this.queuedTasks.length = 0;
524
+ this.queuedTaskByInstance.clear();
525
+ this.taskByHighlightKey.clear();
526
+ this.activeTaskById.clear();
527
+ this.highlighter = void 0;
528
+ this.initialized = false;
529
+ this.workersFailed = false;
530
+ this.queueBroadcastStateChanges();
531
+ }
532
+ isCurrentLifecycle(lifecycleGeneration) {
533
+ return this.lifecycleGeneration === lifecycleGeneration;
534
+ }
535
+ queueInitialization(languages) {
536
+ this.initialize(languages).catch((error) => {
537
+ if (isHandledWorkerPoolError(error)) return;
538
+ console.error(error);
539
+ });
540
+ }
541
+ cancelActiveWorkerTasks() {
542
+ const error = new WorkerPoolTerminatedError();
543
+ for (const task of this.activeTaskById.values()) if ("reject" in task) task.reject(error);
544
+ else if (isRenderTask(task)) this.rejectRenderTaskCallbacks(task, error);
545
+ }
546
+ terminateWorkers() {
547
+ for (const managedWorker of this.workers) managedWorker.worker.terminate();
548
+ this.workers.length = 0;
549
+ }
550
+ getStats() {
551
+ return {
552
+ managerState: (() => {
553
+ if (this.initialized === false) return "waiting";
554
+ if (this.initialized !== true) return "initializing";
555
+ return "initialized";
556
+ })(),
557
+ totalWorkers: this.workers.length,
558
+ workersFailed: this.workersFailed,
559
+ busyWorkers: this.workers.filter((w) => w.requestId != null || w.pendingSetupRequestId != null).length,
560
+ queuedTasks: this.queuedTasks.length,
561
+ activeTasks: this.activeTaskById.size,
562
+ themeSubscribers: this.themeSubscribers.size,
563
+ fileCacheSize: this.fileCache.size,
564
+ diffCacheSize: this.diffCache.size
565
+ };
566
+ }
567
+ submitTask(instance, request) {
568
+ if (this.initialized === false) this.queueInitialization();
569
+ const highlightKey = this.getHighlightKeyForRequest(request);
570
+ const existingTask = highlightKey != null ? this.getTaskByHighlightKey(highlightKey) : void 0;
571
+ if (existingTask != null) {
572
+ this.detachInstanceFromQueuedTasks(instance, existingTask);
573
+ this.addInstanceToTask(existingTask, instance);
574
+ this.queueBroadcastStateChanges();
575
+ return;
576
+ }
577
+ this.detachInstanceFromQueuedTasks(instance);
578
+ const id = this.generateRequestId();
579
+ const requestStart = Date.now();
580
+ const { renderOptionsVersion } = this;
581
+ const task = (() => {
582
+ switch (request.type) {
583
+ case "file": return {
584
+ type: "file",
585
+ id,
586
+ request: {
587
+ ...request,
588
+ id
589
+ },
590
+ instances: /* @__PURE__ */ new Set([instance]),
591
+ primeCache: false,
592
+ highlightKey,
593
+ callbacks: /* @__PURE__ */ new Set(),
594
+ renderOptionsVersion,
595
+ requestStart
596
+ };
597
+ case "diff": return {
598
+ type: "diff",
599
+ id,
600
+ request: {
601
+ ...request,
602
+ id
603
+ },
604
+ instances: /* @__PURE__ */ new Set([instance]),
605
+ primeCache: false,
606
+ highlightKey,
607
+ callbacks: /* @__PURE__ */ new Set(),
608
+ renderOptionsVersion,
609
+ requestStart
610
+ };
611
+ }
612
+ })();
613
+ this.enqueueRenderTask(task, instance);
614
+ }
615
+ submitCacheTask(request, highlightKey) {
616
+ if (this.initialized === false) this.queueInitialization();
617
+ const id = this.generateRequestId();
618
+ const requestStart = Date.now();
619
+ const { renderOptionsVersion } = this;
620
+ const task = (() => {
621
+ switch (request.type) {
622
+ case "file": return {
623
+ type: "file",
624
+ id,
625
+ request: {
626
+ ...request,
627
+ id
628
+ },
629
+ instances: /* @__PURE__ */ new Set(),
630
+ primeCache: true,
631
+ highlightKey,
632
+ callbacks: /* @__PURE__ */ new Set(),
633
+ renderOptionsVersion,
634
+ requestStart
635
+ };
636
+ case "diff": return {
637
+ type: "diff",
638
+ id,
639
+ request: {
640
+ ...request,
641
+ id
642
+ },
643
+ instances: /* @__PURE__ */ new Set(),
644
+ primeCache: true,
645
+ highlightKey,
646
+ callbacks: /* @__PURE__ */ new Set(),
647
+ renderOptionsVersion,
648
+ requestStart
649
+ };
650
+ }
651
+ })();
652
+ this.enqueueRenderTask(task);
653
+ return task;
654
+ }
655
+ enqueueRenderTask(task, instance) {
656
+ this.queuedTasks.push(task);
657
+ if (instance != null) this.queuedTaskByInstance.set(instance, task);
658
+ if (task.highlightKey != null) this.taskByHighlightKey.set(task.highlightKey, task);
659
+ this.queueDrain();
660
+ }
661
+ async resolveLanguagesAndExecuteTask(availableWorker, task, langs) {
662
+ try {
663
+ const mainThreadLangs = langs.filter((lang) => !areLanguagesAttached(lang));
664
+ if (mainThreadLangs.length > 0) getSharedHighlighter({
665
+ themes: getThemes(this.renderOptions.theme),
666
+ langs: ["text", ...mainThreadLangs],
667
+ preferredHighlighter: this.preferredHighlighter
668
+ }).catch((error) => {
669
+ console.error(error);
670
+ });
671
+ const workerMissingLangs = langs.filter((lang) => !availableWorker.langs.has(lang));
672
+ if (workerMissingLangs.length > 0) {
673
+ if (hasResolvedLanguages(workerMissingLangs)) task.request.resolvedLanguages = getResolvedLanguages(workerMissingLangs);
674
+ else task.request.resolvedLanguages = await resolveLanguages(workerMissingLangs);
675
+ }
676
+ if (!this.activeTaskById.has(task.id)) {
677
+ if (availableWorker.requestId === task.id) {
678
+ this.cleanWorkerAndTask(availableWorker, task);
679
+ this.queueBroadcastStateChanges();
680
+ if (this.queuedTasks.length > 0) this.queueDrain();
681
+ }
682
+ return;
683
+ }
684
+ this.executeTask(availableWorker, task);
685
+ } catch (error) {
686
+ this.rejectRenderTaskCallbacks(task, normalizeWorkerError(error));
687
+ this.cleanWorkerAndTask(availableWorker, task);
688
+ this.queueBroadcastStateChanges();
689
+ if (this.queuedTasks.length > 0) this.queueDrain();
690
+ }
691
+ }
692
+ handleWorkerMessage(managedWorker, response) {
693
+ const task = this.activeTaskById.get(response.id);
694
+ try {
695
+ if (task == null) throw IGNORE_RESPONSE;
696
+ else if (response.type === "error") {
697
+ const error = new Error(response.error);
698
+ if (response.stack) error.stack = response.stack;
699
+ if ("reject" in task) task.reject(error);
700
+ else if (isRenderTask(task)) {
701
+ this.notifyHighlightError(task, error);
702
+ this.rejectRenderTaskCallbacks(task, error);
703
+ } else throw new Error("handleWorkerMessage: unknown task type");
704
+ throw error;
705
+ } else {
706
+ if (isRenderTask(task)) for (const { name } of task.request.resolvedLanguages ?? []) managedWorker.langs.add(name);
707
+ switch (response.requestType) {
708
+ case "initialize":
709
+ if (task.type !== "initialize") throw new Error("handleWorkerMessage: task/response dont match");
710
+ this.syncCustomExtensionVersion(managedWorker, task.request);
711
+ task.resolve();
712
+ break;
713
+ case "set-render-options":
714
+ if (task.type !== "set-render-options") throw new Error("handleWorkerMessage: task/response dont match");
715
+ task.resolve();
716
+ break;
717
+ case "file": {
718
+ if (task.type !== "file") throw new Error("handleWorkerMessage: task/response dont match");
719
+ const { result, options } = response;
720
+ if (!this.isCurrentRenderTask(task) || !areFileRenderOptionsEqual(options, this.getFileRenderOptions())) throw IGNORE_RESPONSE;
721
+ const { request } = task;
722
+ this.syncCustomExtensionVersion(managedWorker, request);
723
+ if (task.cacheKeyAtDispatch != null) this.fileCache.set(task.cacheKeyAtDispatch, {
724
+ result,
725
+ options
726
+ });
727
+ this.resolveRenderTaskCallbacks(task);
728
+ this.notifyFileInstances(task, result, options);
729
+ break;
730
+ }
731
+ case "diff": {
732
+ if (task.type !== "diff") throw new Error("handleWorkerMessage: task/response dont match");
733
+ const { result, options } = response;
734
+ if (!this.isCurrentRenderTask(task) || !areDiffRenderOptionsEqual(options, this.getDiffRenderOptions())) throw IGNORE_RESPONSE;
735
+ const { request } = task;
736
+ this.syncCustomExtensionVersion(managedWorker, request);
737
+ if (task.cacheKeyAtDispatch != null) this.diffCache.set(task.cacheKeyAtDispatch, {
738
+ result,
739
+ options
740
+ });
741
+ this.resolveRenderTaskCallbacks(task);
742
+ this.notifyDiffInstances(task, result, options);
743
+ break;
744
+ }
745
+ }
746
+ }
747
+ } catch (error) {
748
+ if (error !== IGNORE_RESPONSE) console.error(error, task, response);
749
+ }
750
+ this.cleanWorkerAndTask(managedWorker, task, response.id);
751
+ this.queueBroadcastStateChanges();
752
+ if (this.queuedTasks.length > 0) this.queueDrain();
753
+ }
754
+ _queuedDrain;
755
+ queueDrain() {
756
+ if (this._queuedDrain != null) return;
757
+ this._queuedDrain = Promise.resolve().then(this.drainQueue);
758
+ this.queueBroadcastStateChanges();
759
+ }
760
+ assignWorkerToTask(task, managedWorker) {
761
+ managedWorker.requestId = task.id;
762
+ if (isRenderTask(task)) {
763
+ this.clearQueuedInstanceRequests(task);
764
+ this.trackInstanceRequests(task);
765
+ }
766
+ this.activeTaskById.set(task.id, task);
767
+ }
768
+ cleanWorkerAndTask(managedWorker, task, requestId = task?.id) {
769
+ if (managedWorker.requestId === requestId) managedWorker.requestId = void 0;
770
+ if (managedWorker.pendingSetupRequestId === requestId) managedWorker.pendingSetupRequestId = void 0;
771
+ if (task != null) {
772
+ if (isRenderTask(task)) {
773
+ this.rejectRenderTaskCallbacks(task, new WorkerPoolTaskCanceledError());
774
+ this.clearInstanceRequests(task);
775
+ this.clearHighlightKey(task);
776
+ }
777
+ this.activeTaskById.delete(task.id);
778
+ }
779
+ }
780
+ executeTask(managedWorker, task) {
781
+ if (shouldSyncCustomExtensions(task.request)) this.maybeAttachCustomExtensions(managedWorker, task.request);
782
+ if (!this.activeTaskById.has(task.id)) this.assignWorkerToTask(task, managedWorker);
783
+ try {
784
+ if (task.type === "file") task.cacheKeyAtDispatch = task.request.file.cacheKey;
785
+ else if (task.type === "diff") task.cacheKeyAtDispatch = task.request.diff.cacheKey;
786
+ managedWorker.worker.postMessage(task.request);
787
+ } catch (error) {
788
+ console.error("Failed to post message to worker:", error);
789
+ if (isRenderTask(task)) {
790
+ this.notifyHighlightError(task, error);
791
+ this.rejectRenderTaskCallbacks(task, normalizeWorkerError(error));
792
+ } else if ("reject" in task) task.reject(error);
793
+ this.cleanWorkerAndTask(managedWorker, task);
794
+ if (this.queuedTasks.length > 0) this.queueDrain();
795
+ }
796
+ this.queueBroadcastStateChanges();
797
+ }
798
+ maybeAttachCustomExtensions(managedWorker, request) {
799
+ if (request.customExtensionsVersion != null) return;
800
+ const version = getCustomExtensionsVersion();
801
+ if (managedWorker.customExtensionsVersion >= version) return;
802
+ request.customExtensionsVersion = version;
803
+ request.customExtensionMap = getCustomExtensionsMap();
804
+ }
805
+ syncCustomExtensionVersion(managedWorker, request) {
806
+ if (request.customExtensionsVersion == null) return;
807
+ managedWorker.customExtensionsVersion = request.customExtensionsVersion;
808
+ }
809
+ getAvailableWorker(langs) {
810
+ let worker;
811
+ for (const managedWorker of this.workers) {
812
+ if (managedWorker.requestId != null || managedWorker.pendingSetupRequestId != null || !managedWorker.initialized) continue;
813
+ worker = managedWorker;
814
+ if (langs.length === 0) break;
815
+ let hasEveryLang = true;
816
+ for (const lang of langs) if (!managedWorker.langs.has(lang)) {
817
+ hasEveryLang = false;
818
+ break;
819
+ }
820
+ if (hasEveryLang) break;
821
+ }
822
+ return worker;
823
+ }
824
+ getFileHighlightKey(file) {
825
+ if (file.cacheKey == null) return;
826
+ return `file:${file.cacheKey}:${this.renderOptionsVersion}`;
827
+ }
828
+ getDiffHighlightKey(diff) {
829
+ if (diff.cacheKey == null) return;
830
+ return `diff:${diff.cacheKey}:${this.renderOptionsVersion}`;
831
+ }
832
+ getHighlightKeyForRequest(request) {
833
+ switch (request.type) {
834
+ case "file": return this.getFileHighlightKey(request.file);
835
+ case "diff": return this.getDiffHighlightKey(request.diff);
836
+ }
837
+ }
838
+ hasActiveRequest(task) {
839
+ for (const instance of getInstances(task)) if (this.activeRequestByInstance.has(instance)) return true;
840
+ return false;
841
+ }
842
+ addInstanceToTask(task, instance) {
843
+ if (task.type === "file") task.instances.add(instance);
844
+ else task.instances.add(instance);
845
+ if (this.activeTaskById.has(task.id)) this.activeRequestByInstance.set(instance, task.id);
846
+ else this.queuedTaskByInstance.set(instance, task);
847
+ }
848
+ detachInstanceFromQueuedTasks(instance, exceptTask) {
849
+ const task = this.queuedTaskByInstance.get(instance);
850
+ if (task == null || task === exceptTask) return;
851
+ this.queuedTaskByInstance.delete(instance);
852
+ this.detachInstanceFromRenderTask(task, instance);
853
+ if (!task.primeCache && task.instances.size === 0) this.removeQueuedTask(task);
854
+ }
855
+ detachInstanceFromRenderTask(task, instance) {
856
+ if (task.type === "file") task.instances.delete(instance);
857
+ else task.instances.delete(instance);
858
+ }
859
+ removeQueuedTask(task) {
860
+ const index = this.queuedTasks.indexOf(task);
861
+ if (index !== -1) this.queuedTasks.splice(index, 1);
862
+ this.rejectRenderTaskCallbacks(task, new WorkerPoolTaskCanceledError());
863
+ this.clearQueuedInstanceRequests(task);
864
+ this.clearHighlightKey(task);
865
+ }
866
+ removeActiveTask(task) {
867
+ this.rejectRenderTaskCallbacks(task, new WorkerPoolTaskCanceledError());
868
+ this.clearInstanceRequests(task);
869
+ this.clearHighlightKey(task);
870
+ this.activeTaskById.delete(task.id);
871
+ }
872
+ createRenderTaskCallbacks(task) {
873
+ return new Promise((resolve, reject) => {
874
+ task.callbacks.add({
875
+ resolve,
876
+ reject
877
+ });
878
+ });
879
+ }
880
+ resolveRenderTaskCallbacks(task) {
881
+ for (const callbacks of task.callbacks) callbacks.resolve();
882
+ task.callbacks.clear();
883
+ }
884
+ rejectRenderTaskCallbacks(task, error) {
885
+ for (const callbacks of task.callbacks) callbacks.reject(error);
886
+ task.callbacks.clear();
887
+ }
888
+ invalidateRenderTasks() {
889
+ for (let index = this.queuedTasks.length - 1; index >= 0; index--) {
890
+ const task = this.queuedTasks[index];
891
+ if (task.renderOptionsVersion !== this.renderOptionsVersion) this.removeQueuedTask(task);
892
+ }
893
+ for (const task of Array.from(this.activeTaskById.values())) if (isRenderTask(task) && task.renderOptionsVersion !== this.renderOptionsVersion) this.removeActiveTask(task);
894
+ }
895
+ clearQueuedInstanceRequests(task) {
896
+ for (const instance of getInstances(task)) if (this.queuedTaskByInstance.get(instance) === task) this.queuedTaskByInstance.delete(instance);
897
+ }
898
+ clearHighlightKey(task) {
899
+ if (task.highlightKey != null && this.taskByHighlightKey.get(task.highlightKey) === task) this.taskByHighlightKey.delete(task.highlightKey);
900
+ }
901
+ trackInstanceRequests(task) {
902
+ for (const instance of getInstances(task)) this.activeRequestByInstance.set(instance, task.id);
903
+ }
904
+ clearInstanceRequests(task) {
905
+ for (const instance of getInstances(task)) if (this.activeRequestByInstance.get(instance) === task.id) this.activeRequestByInstance.delete(instance);
906
+ }
907
+ notifyFileInstances(task, result, options) {
908
+ for (const instance of task.instances) if (this.activeRequestByInstance.get(instance) === task.id) instance.onHighlightSuccess(task.request.file, result, options);
909
+ }
910
+ notifyDiffInstances(task, result, options) {
911
+ for (const instance of task.instances) if (this.activeRequestByInstance.get(instance) === task.id) instance.onHighlightSuccess(task.request.diff, result, options);
912
+ }
913
+ notifyHighlightError(task, error) {
914
+ for (const instance of getInstances(task)) if (this.activeRequestByInstance.get(instance) === task.id) instance.onHighlightError(error);
915
+ }
916
+ hasMatchingFileInstanceTask(instance, file) {
917
+ for (const task of this.iterateRenderTasks()) if (task.type === "file" && this.isCurrentRenderTask(task) && task.instances.has(instance) && areFilesEqual(file, task.request.file)) return true;
918
+ return false;
919
+ }
920
+ hasMatchingDiffInstanceTask(instance, diff) {
921
+ for (const task of this.iterateRenderTasks()) if (task.type === "diff" && this.isCurrentRenderTask(task) && task.instances.has(instance) && areDiffTargetsEqual(task.request.diff, diff)) return true;
922
+ return false;
923
+ }
924
+ getTaskByHighlightKey(highlightKey) {
925
+ const task = this.taskByHighlightKey.get(highlightKey);
926
+ return task != null && this.isCurrentRenderTask(task) ? task : void 0;
927
+ }
928
+ isCurrentRenderTask(task) {
929
+ return task.renderOptionsVersion === this.renderOptionsVersion;
930
+ }
931
+ *iterateRenderTasks() {
932
+ for (const task of this.queuedTasks) yield task;
933
+ for (const task of this.activeTaskById.values()) if (isRenderTask(task)) yield task;
934
+ }
935
+ generateRequestId() {
936
+ return `req_${++this.nextRequestId}`;
937
+ }
938
+ };
939
+ function shouldSyncCustomExtensions(request) {
940
+ return request.type === "initialize" || request.type === "file" || request.type === "diff";
941
+ }
942
+ function getLangsFromTask(task) {
943
+ const langs = /* @__PURE__ */ new Set();
944
+ if (task.type === "initialize" || task.type === "set-render-options") return [];
945
+ switch (task.type) {
946
+ case "file":
947
+ langs.add(task.request.file.lang ?? getFiletypeFromFileName(task.request.file.name));
948
+ break;
949
+ case "diff":
950
+ langs.add(task.request.diff.lang ?? getFiletypeFromFileName(task.request.diff.name));
951
+ langs.add(task.request.diff.lang ?? getFiletypeFromFileName(task.request.diff.prevName ?? "-"));
952
+ }
953
+ langs.delete("text");
954
+ return Array.from(langs);
955
+ }
956
+ function isRenderTask(task) {
957
+ return task?.type === "file" || task?.type === "diff";
958
+ }
959
+ function getInstances(task) {
960
+ return task.instances;
961
+ }
962
+ function normalizeWorkerError(error) {
963
+ if (error instanceof Error) return error;
964
+ if (error != null && typeof error === "object") {
965
+ if ("error" in error && error.error instanceof Error) return error.error;
966
+ if ("message" in error && typeof error.message === "string") return new Error(error.message);
967
+ }
968
+ return new Error(String(error));
969
+ }
970
+ function errorFromWorkerErrorEvent(event) {
971
+ const cause = "error" in event ? event.error : void 0;
972
+ const message = "message" in event && typeof event.message === "string" && event.message !== "" ? event.message : cause instanceof Error ? cause.message : "the worker script failed to load (the error event carries no message)";
973
+ return new WorkerPoolWorkerError(`WorkerPoolManager: worker failed: ${message}`, cause instanceof Error ? { cause } : void 0);
974
+ }
975
+ //#endregion
976
+ //#region ../ui/src/lib/files/code-workers.ts
977
+ /**
978
+ * Create one pool per workspace in a browser lifecycle, then lend it to its viewers.
979
+ * @param options - Pool configuration and the owning workspace's lifetime signal.
980
+ * @returns The borrowed manager, observable initialization, and idempotent disposal.
981
+ */
982
+ async function createCodeWorkerPool(options = {}) {
983
+ let { signal, renderOptions, ...poolOptions } = options;
984
+ signal?.throwIfAborted();
985
+ let workerFactory = options.workerFactory ?? (await import("./worker-factory-C80HGMWf.js")).createCodeWorker;
986
+ signal?.throwIfAborted();
987
+ let pool = new WorkerPoolManager({
988
+ poolSize: 2,
989
+ ...poolOptions,
990
+ workerFactory
991
+ }, renderOptions ?? {});
992
+ let disposed = false;
993
+ function dispose() {
994
+ if (disposed) return;
995
+ disposed = true;
996
+ signal?.removeEventListener("abort", dispose);
997
+ pool.terminate();
998
+ }
999
+ signal?.addEventListener("abort", dispose, { once: true });
1000
+ return {
1001
+ pool,
1002
+ ready: pool.initialize().then(() => {
1003
+ if (disposed) pool.terminate();
1004
+ }),
1005
+ dispose
1006
+ };
1007
+ }
1008
+ //#endregion
1009
+ //#region app/client/files-request.ts
1010
+ var endpoints = new Set([
1011
+ routes.filesApi,
1012
+ routes.fileSearchApi,
1013
+ routes.fileReviewApi,
1014
+ routes.fileMutationsApi,
1015
+ routes.fileSavesApi,
1016
+ routes.fileReviewActionsApi
1017
+ ].flatMap((group) => Object.values(group).map((route) => route.href())));
1018
+ /** A Files request that failed, with the service's error code and the HTTP status. */
1019
+ var FilesRequestError = class extends Error {
1020
+ code;
1021
+ status;
1022
+ constructor(message, code = "request", status = 0) {
1023
+ super(message);
1024
+ this.name = "FilesRequestError";
1025
+ this.code = code;
1026
+ this.status = status;
1027
+ }
1028
+ };
1029
+ /**
1030
+ * Reads one bounded, same-origin Files endpoint with the project capability.
1031
+ * @param url - Relative endpoint from the application's route contract.
1032
+ * @param token - Current project's capability; never sent to previewed pages or redirects.
1033
+ * @param signal - Owner's request lifetime.
1034
+ * @param parse - Domain DTO validator.
1035
+ * @param fetcher - Injectable transport for browser-free boundary tests.
1036
+ * @param action - JSON payload, accepted only by the explicit mutation POST endpoints.
1037
+ * @returns A validated value or explicit request error, never an invented empty success.
1038
+ */
1039
+ async function requestFiles(url, token, signal, parse, fetcher = fetch, action) {
1040
+ let parsed = new URL(url, "http://bud.invalid");
1041
+ let mutation = parsed.pathname === "/api/files/mutations/prepare" || parsed.pathname === "/api/files/mutations/apply" || parsed.pathname === "/api/files/review-actions/prepare" || parsed.pathname === "/api/files/review-actions/apply" || parsed.pathname === "/api/files/save";
1042
+ if (!url.startsWith("/api/files") || url.startsWith("//") || parsed.origin !== "http://bud.invalid" || parsed.hash || !endpoints.has(parsed.pathname) || (parsed.pathname === "/api/files/refresh" || parsed.pathname === "/api/files/save" || parsed.pathname.startsWith("/api/files/review-actions/") || parsed.pathname.startsWith("/api/files/mutations/")) && parsed.search || mutation !== Boolean(action)) throw new FilesRequestError("Invalid file endpoint.", "input");
1043
+ signal.throwIfAborted();
1044
+ let response = await fetcher(url, {
1045
+ method: mutation || parsed.pathname === "/api/files/refresh" ? "POST" : "GET",
1046
+ headers: {
1047
+ "X-Bud-Token": token,
1048
+ ...mutation ? { "Content-Type": "application/json" } : {}
1049
+ },
1050
+ ...action ? { body: JSON.stringify(action.body) } : {},
1051
+ credentials: "same-origin",
1052
+ redirect: "error",
1053
+ cache: "no-store",
1054
+ signal
1055
+ });
1056
+ let reader = response.body?.getReader();
1057
+ if (!reader) throw new FilesRequestError("The file service returned an empty response.", "response", response.status);
1058
+ let chunks = [];
1059
+ let size = 0;
1060
+ let cancel = () => {
1061
+ reader.cancel(signal.reason).catch(() => {});
1062
+ };
1063
+ signal.addEventListener("abort", cancel, { once: true });
1064
+ try {
1065
+ while (true) {
1066
+ signal.throwIfAborted();
1067
+ let part = await reader.read();
1068
+ signal.throwIfAborted();
1069
+ if (part.done) break;
1070
+ size += part.value.byteLength;
1071
+ if (size > 16777216) throw new FilesRequestError("This file response exceeds the display limit. Narrow the file view and try again.", "limit", response.status);
1072
+ chunks.push(part.value);
1073
+ }
1074
+ } catch (error) {
1075
+ await reader.cancel().catch(() => {});
1076
+ throw error;
1077
+ } finally {
1078
+ signal.removeEventListener("abort", cancel);
1079
+ reader.releaseLock();
1080
+ }
1081
+ let bytes = new Uint8Array(size);
1082
+ let offset = 0;
1083
+ for (let chunk of chunks) {
1084
+ bytes.set(chunk, offset);
1085
+ offset += chunk.byteLength;
1086
+ }
1087
+ let value;
1088
+ try {
1089
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1090
+ } catch {
1091
+ throw new FilesRequestError("The file service returned invalid data. Refresh and try again.", "response", response.status);
1092
+ }
1093
+ if (!response.ok) throw new FilesRequestError(value && typeof value === "object" && "error" in value && typeof value.error === "string" ? value.error.slice(0, 1e3) : "Could not load files. Try again.", value && typeof value === "object" && "code" in value && typeof value.code === "string" ? value.code.slice(0, 100) : "request", response.status);
1094
+ try {
1095
+ return parse(value);
1096
+ } catch {
1097
+ throw new FilesRequestError("The file service returned unexpected data. Refresh and try again.", "response", response.status);
1098
+ }
1099
+ }
1100
+ //#endregion
1101
+ //#region app/client/file-mutation-controller.ts
1102
+ /**
1103
+ * Collects the guards of the open views and runs them, in order, around every file action.
1104
+ * @returns The guard set: register a guard for its lifetime, then run them before and after.
1105
+ */
1106
+ function createFileMutationGuards() {
1107
+ let guards = /* @__PURE__ */ new Set();
1108
+ return {
1109
+ register(guard, signal) {
1110
+ if (signal.aborted) return;
1111
+ guards.add(guard);
1112
+ signal.addEventListener("abort", () => guards.delete(guard), { once: true });
1113
+ },
1114
+ async beforeApply(intent, plan, signal) {
1115
+ for (let guard of guards) {
1116
+ signal.throwIfAborted();
1117
+ let reason = await guard.beforeApply(intent, plan, signal);
1118
+ signal.throwIfAborted();
1119
+ if (reason) throw new Error(reason);
1120
+ }
1121
+ },
1122
+ async afterApply(intent, result) {
1123
+ for (let guard of guards) await guard.afterApply(intent, result);
1124
+ }
1125
+ };
1126
+ }
1127
+ function message$4(cause) {
1128
+ return cause instanceof Error ? cause.message : "The file action could not be completed.";
1129
+ }
1130
+ function assertPlan(intent, plan) {
1131
+ if (intent.kind !== plan.kind) throw new Error("The service returned a different file action.");
1132
+ if (intent.kind === "restore") return;
1133
+ let expected = "entries" in intent ? intent.entries.map((item) => [item.path, item.destination]) : intent.kind === "delete" ? intent.paths.map((source) => [source, null]) : [[null, intent.path]];
1134
+ let actual = plan.items.map((item) => [item.source, item.destination]);
1135
+ if (JSON.stringify(expected) !== JSON.stringify(actual)) throw new Error("The service returned different file paths. Refresh before continuing.");
1136
+ }
1137
+ function assertResult(plan, result) {
1138
+ if (result.id !== plan.id || result.items.length !== plan.items.length || result.items.some((item, index) => item.source !== plan.items[index].source || item.destination !== plan.items[index].destination)) throw new Error("The file result does not match its confirmed plan. Refresh and check Recovery before retrying.");
1139
+ }
1140
+ /**
1141
+ * Coordinates validated plans without hiding partial results or retrying writes.
1142
+ * @param options - Scoped transport, draft guards and authoritative refresh callbacks.
1143
+ * @returns The dialog state and deliberate action/recovery commands.
1144
+ */
1145
+ function createFileMutationController(options) {
1146
+ let state = null;
1147
+ let recoveryOpen = false;
1148
+ let recoveries = null;
1149
+ let recoveryLoading = false;
1150
+ let recoveryError = "";
1151
+ let request;
1152
+ let recoveryRequest;
1153
+ let intent = null;
1154
+ let sequence = 0;
1155
+ function changed() {
1156
+ if (!options.signal.aborted) options.onChange();
1157
+ }
1158
+ function busy() {
1159
+ return state?.phase === "preparing" || state?.phase === "applying";
1160
+ }
1161
+ function close() {
1162
+ if (state?.phase === "applying") return;
1163
+ request?.abort();
1164
+ sequence++;
1165
+ state = null;
1166
+ intent = null;
1167
+ changed();
1168
+ }
1169
+ async function prepare(current, applyImmediately) {
1170
+ if (!state || state.phase === "applying") return;
1171
+ request?.abort();
1172
+ let controller = new AbortController();
1173
+ request = controller;
1174
+ let signal = AbortSignal.any([options.signal, controller.signal]);
1175
+ let turn = ++sequence;
1176
+ intent = current;
1177
+ state = {
1178
+ ...state,
1179
+ phase: "preparing",
1180
+ plan: null,
1181
+ result: null,
1182
+ error: ""
1183
+ };
1184
+ changed();
1185
+ try {
1186
+ let plan = await options.transport.prepare(current, signal);
1187
+ signal.throwIfAborted();
1188
+ assertPlan(current, plan);
1189
+ if (turn !== sequence || !state) return;
1190
+ state = {
1191
+ ...state,
1192
+ plan,
1193
+ phase: "confirming"
1194
+ };
1195
+ changed();
1196
+ if (applyImmediately) await apply();
1197
+ } catch (cause) {
1198
+ if (!signal.aborted && turn === sequence && state) {
1199
+ state = {
1200
+ ...state,
1201
+ phase: "editing",
1202
+ error: message$4(cause)
1203
+ };
1204
+ changed();
1205
+ }
1206
+ }
1207
+ }
1208
+ async function apply() {
1209
+ if (!state?.plan || !intent || busy()) return;
1210
+ let current = intent;
1211
+ let plan = state.plan;
1212
+ if (Date.parse(plan.expiresAt) <= Date.now()) {
1213
+ state = {
1214
+ ...state,
1215
+ phase: "editing",
1216
+ plan: null,
1217
+ error: "This confirmation expired. Review the current files again."
1218
+ };
1219
+ changed();
1220
+ return;
1221
+ }
1222
+ request?.abort();
1223
+ let controller = new AbortController();
1224
+ request = controller;
1225
+ let signal = AbortSignal.any([options.signal, controller.signal]);
1226
+ let turn = ++sequence;
1227
+ let sent = false;
1228
+ state = {
1229
+ ...state,
1230
+ phase: "applying",
1231
+ error: ""
1232
+ };
1233
+ changed();
1234
+ try {
1235
+ await options.guards.beforeApply(current, plan, signal);
1236
+ signal.throwIfAborted();
1237
+ sent = true;
1238
+ let result = await options.transport.apply(plan, signal);
1239
+ signal.throwIfAborted();
1240
+ assertResult(plan, result);
1241
+ if (turn !== sequence || !state) return;
1242
+ state = {
1243
+ ...state,
1244
+ phase: "result",
1245
+ result
1246
+ };
1247
+ let draftError = "";
1248
+ try {
1249
+ await options.guards.afterApply(current, result);
1250
+ } catch (cause) {
1251
+ draftError = `Files changed, but retained draft metadata could not be updated. ${message$4(cause)}`;
1252
+ }
1253
+ await options.onApplied(current, result);
1254
+ if (turn !== sequence || !state) return;
1255
+ state = {
1256
+ ...state,
1257
+ error: draftError
1258
+ };
1259
+ if (result.status === "applied" && !draftError && !["delete", "restore"].includes(current.kind)) {
1260
+ state = null;
1261
+ intent = null;
1262
+ }
1263
+ changed();
1264
+ } catch (cause) {
1265
+ if (signal.aborted || turn !== sequence || !state) return;
1266
+ state = {
1267
+ ...state,
1268
+ phase: "editing",
1269
+ plan: null,
1270
+ error: message$4(cause)
1271
+ };
1272
+ if (sent) {
1273
+ await options.onUncertain().catch(() => {});
1274
+ if (state && turn === sequence) state = {
1275
+ ...state,
1276
+ error: `${message$4(cause)} Refresh and check Recovery before retrying.`
1277
+ };
1278
+ }
1279
+ changed();
1280
+ }
1281
+ }
1282
+ function open(next, runImmediately = false) {
1283
+ if (busy()) return;
1284
+ request?.abort();
1285
+ recoveryRequest?.abort();
1286
+ sequence++;
1287
+ recoveryOpen = false;
1288
+ intent = null;
1289
+ let destination = "";
1290
+ let error = "";
1291
+ try {
1292
+ destination = defaultFileDestination(next, options.index());
1293
+ } catch (cause) {
1294
+ error = message$4(cause);
1295
+ }
1296
+ state = {
1297
+ request: next,
1298
+ destination,
1299
+ phase: "editing",
1300
+ plan: null,
1301
+ result: null,
1302
+ error
1303
+ };
1304
+ changed();
1305
+ if (!error && (next.action === "delete" || next.action === "restore" || runImmediately)) submit();
1306
+ }
1307
+ async function submit() {
1308
+ if (!state || busy()) return;
1309
+ if (state.phase === "confirming") {
1310
+ await apply();
1311
+ return;
1312
+ }
1313
+ try {
1314
+ let next = state.result && intent ? remainingMutationIntent(intent, state.result) : fileActionIntent(state.request, state.destination, options.index());
1315
+ if (!next) return;
1316
+ await prepare(next, !["delete", "restore"].includes(next.kind));
1317
+ } catch (cause) {
1318
+ if (state) state = {
1319
+ ...state,
1320
+ error: message$4(cause),
1321
+ phase: "editing"
1322
+ };
1323
+ changed();
1324
+ }
1325
+ }
1326
+ async function loadRecoveries() {
1327
+ recoveryRequest?.abort();
1328
+ let controller = new AbortController();
1329
+ recoveryRequest = controller;
1330
+ let signal = AbortSignal.any([options.signal, controller.signal]);
1331
+ recoveryLoading = true;
1332
+ recoveryError = "";
1333
+ changed();
1334
+ try {
1335
+ let next = await options.transport.recoveries(signal);
1336
+ if (!signal.aborted) recoveries = next;
1337
+ } catch (cause) {
1338
+ if (!signal.aborted) recoveryError = message$4(cause);
1339
+ } finally {
1340
+ if (recoveryRequest === controller) {
1341
+ recoveryLoading = false;
1342
+ changed();
1343
+ }
1344
+ }
1345
+ }
1346
+ options.signal.addEventListener("abort", () => {
1347
+ request?.abort();
1348
+ recoveryRequest?.abort();
1349
+ }, { once: true });
1350
+ return {
1351
+ get state() {
1352
+ return state;
1353
+ },
1354
+ get busy() {
1355
+ return busy();
1356
+ },
1357
+ get recovery() {
1358
+ return {
1359
+ open: recoveryOpen,
1360
+ recoveries,
1361
+ loading: recoveryLoading,
1362
+ error: recoveryError
1363
+ };
1364
+ },
1365
+ open,
1366
+ close,
1367
+ submit,
1368
+ destination(value) {
1369
+ if (!state || busy()) return;
1370
+ intent = null;
1371
+ state = {
1372
+ ...state,
1373
+ destination: value,
1374
+ plan: null,
1375
+ result: null,
1376
+ phase: "editing",
1377
+ error: ""
1378
+ };
1379
+ changed();
1380
+ },
1381
+ recoveries() {
1382
+ if (busy()) return;
1383
+ close();
1384
+ recoveryOpen = true;
1385
+ loadRecoveries();
1386
+ changed();
1387
+ },
1388
+ refreshRecoveries: loadRecoveries,
1389
+ closeRecoveries() {
1390
+ recoveryRequest?.abort();
1391
+ recoveryOpen = false;
1392
+ changed();
1393
+ }
1394
+ };
1395
+ }
1396
+ //#endregion
1397
+ //#region app/client/project-file-review-data.ts
1398
+ /**
1399
+ * Writes a review comparison as URL parameters.
1400
+ * @param comparison - The working tree, the staged changes or two commits.
1401
+ * @returns The parameters.
1402
+ */
1403
+ function comparisonQuery(comparison) {
1404
+ return new URLSearchParams(comparison.mode === "commits" ? {
1405
+ mode: comparison.mode,
1406
+ base: comparison.base,
1407
+ target: comparison.target
1408
+ } : { mode: comparison.mode });
1409
+ }
1410
+ /**
1411
+ * Reads a bounded page with at most three in-flight diffs and a shared cancellation signal.
1412
+ * @param snapshot - Pinned comparison metadata returned by the server.
1413
+ * @param paths - A bounded page of paths belonging to that snapshot.
1414
+ * @param token - Current project capability.
1415
+ * @param signal - Owning selection/request lifetime.
1416
+ * @param fetcher - Injectable transport for deterministic concurrency and stale-data checks.
1417
+ * @returns Exact source snapshots plus explicit per-file failures.
1418
+ */
1419
+ async function readReviewPage(snapshot, paths, token, signal, fetcher = fetch) {
1420
+ if (paths.length > 20 || paths.some((path) => !snapshot.entries.some((entry) => entry.path === path))) throw new FilesRequestError("The selected files are outside this comparison page.", "input");
1421
+ let result = /* @__PURE__ */ new Map();
1422
+ let failures = {};
1423
+ let cursor = 0;
1424
+ let retainedBytes = 0;
1425
+ let controller = new AbortController();
1426
+ let lifetime = AbortSignal.any([signal, controller.signal]);
1427
+ let fatal;
1428
+ async function worker() {
1429
+ try {
1430
+ while (cursor < paths.length) {
1431
+ lifetime.throwIfAborted();
1432
+ let path = paths[cursor++];
1433
+ let query = comparisonQuery(snapshot.comparison);
1434
+ query.set("path", path);
1435
+ query.set("expectedRevision", snapshot.revision);
1436
+ try {
1437
+ let diff = await requestFiles(`${routes.fileReviewApi.diff.href()}?${query}`, token, lifetime, parseReviewDiff, fetcher);
1438
+ if (diff.entry.path !== path || diff.metadataRevision !== snapshot.revision || JSON.stringify(diff.comparison) !== JSON.stringify(snapshot.comparison)) throw new FilesRequestError("The comparison changed while reading. Refresh before continuing.", "changed", 409);
1439
+ let bytes = [
1440
+ diff.oldFile,
1441
+ diff.newFile,
1442
+ diff.conflict?.base,
1443
+ diff.conflict?.ours,
1444
+ diff.conflict?.theirs
1445
+ ].reduce((total, file) => total + (file?.contents?.length ?? 0) * 2, 0);
1446
+ if (retainedBytes + bytes > 33554432) failures[path] = "This page reached its memory limit. Open this file individually.";
1447
+ else {
1448
+ retainedBytes += bytes;
1449
+ result.set(path, diff);
1450
+ }
1451
+ } catch (cause) {
1452
+ if (lifetime.aborted || cause instanceof FilesRequestError && (cause.status === 409 || cause.status === 401 || cause.status === 403)) throw cause;
1453
+ failures[path] = cause instanceof Error ? cause.message : "Could not read this comparison.";
1454
+ }
1455
+ }
1456
+ } catch (cause) {
1457
+ fatal ??= cause;
1458
+ controller.abort(cause);
1459
+ }
1460
+ }
1461
+ await Promise.all(Array.from({ length: Math.min(3, paths.length) }, worker));
1462
+ if (fatal) throw fatal;
1463
+ signal.throwIfAborted();
1464
+ return {
1465
+ diffs: paths.flatMap((path) => result.get(path) ?? []),
1466
+ failures
1467
+ };
1468
+ }
1469
+ //#endregion
1470
+ //#region app/client/file-drafts.ts
1471
+ /** Drafts kept as UI preferences, which stay in this browser. */
1472
+ var preferenceDraftStorage = {
1473
+ getItem: (key) => {
1474
+ let value = readPreference(key);
1475
+ return typeof value === "string" ? value : null;
1476
+ },
1477
+ setItem: (key, value) => writePreference(key, value)
1478
+ };
1479
+ var maxDrafts = 16;
1480
+ var maxDraftBytes = maxDrafts * maxFileSaveBytes * 2;
1481
+ var maxStoredCharacters = 3145728;
1482
+ var encoder = new TextEncoder();
1483
+ /**
1484
+ * Whether a draft differs from the contents it was made against.
1485
+ * @param draft - The draft.
1486
+ * @returns True when it has unsaved changes.
1487
+ */
1488
+ function draftDirty(draft) {
1489
+ return draft.contents !== draft.baseContents;
1490
+ }
1491
+ function bytes(draft) {
1492
+ return encoder.encode(draft.contents).length + encoder.encode(draft.baseContents).length;
1493
+ }
1494
+ function within(path, parent) {
1495
+ return path === parent || path.startsWith(`${parent}/`);
1496
+ }
1497
+ /**
1498
+ * Strict project-bound draft records never authorize writes without a live server revision.
1499
+ * @param value - Serialized local browser record.
1500
+ * @param projectId - Expected connected project identity.
1501
+ * @returns Validated draft entries.
1502
+ */
1503
+ function parseDrafts(value, projectId) {
1504
+ if (!value) return [];
1505
+ if (value.length > maxStoredCharacters) throw new Error("The local draft record exceeds its limit.");
1506
+ let parsed = JSON.parse(value);
1507
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid local draft record.");
1508
+ let record = parsed;
1509
+ if (record.version !== 1 || record.projectId !== projectId || !Array.isArray(record.drafts) || record.drafts.length > maxDrafts || Object.keys(record).some((key) => ![
1510
+ "version",
1511
+ "projectId",
1512
+ "drafts"
1513
+ ].includes(key))) throw new Error("Invalid local draft record.");
1514
+ let seen = /* @__PURE__ */ new Set();
1515
+ let total = 0;
1516
+ return record.drafts.map((input) => {
1517
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Invalid local draft.");
1518
+ let item = input;
1519
+ if (Object.keys(item).some((key) => ![
1520
+ "id",
1521
+ "path",
1522
+ "baseRevision",
1523
+ "baseContents",
1524
+ "contents",
1525
+ "updatedAt"
1526
+ ].includes(key)) || typeof item.id !== "string" || !/^[a-zA-Z0-9-]{1,80}$/.test(item.id) || !Number.isSafeInteger(item.updatedAt) || Number(item.updatedAt) < 0) throw new Error("Invalid local draft.");
1527
+ parseFileSaveRequest({
1528
+ path: item.path,
1529
+ expectedRevision: item.baseRevision,
1530
+ contents: item.baseContents
1531
+ });
1532
+ parseFileSaveRequest({
1533
+ path: item.path,
1534
+ expectedRevision: item.baseRevision,
1535
+ contents: item.contents
1536
+ });
1537
+ let draft = item;
1538
+ if (seen.has(draft.path)) throw new Error("Duplicate local draft path.");
1539
+ seen.add(draft.path);
1540
+ total += bytes(draft);
1541
+ if (total > maxDraftBytes) throw new Error("The local drafts exceed their memory limit.");
1542
+ return { ...draft };
1543
+ });
1544
+ }
1545
+ /**
1546
+ * Bounded memory retains drafts even when browser storage fails. Nothing dirty is silently evicted.
1547
+ * @param projectId - Connected project identity.
1548
+ * @param storage - Optional injectable browser persistence.
1549
+ * @param now - Clock for draft modification times.
1550
+ * @returns Draft storage and explicit mutation guards.
1551
+ */
1552
+ function createFileDrafts(projectId, storage, now = Date.now) {
1553
+ let drafts = /* @__PURE__ */ new Map();
1554
+ let warning = "";
1555
+ let key = `bud:drafts:${projectId}:v1`;
1556
+ try {
1557
+ for (let draft of parseDrafts(storage?.getItem(key) ?? null, projectId)) drafts.set(draft.path, draft);
1558
+ } catch {
1559
+ warning = "Local drafts could not be read. The stored record has been left untouched.";
1560
+ }
1561
+ let unreadable = Boolean(warning);
1562
+ function persist() {
1563
+ if (unreadable) return false;
1564
+ let record = JSON.stringify({
1565
+ version: 1,
1566
+ projectId,
1567
+ drafts: [...drafts.values()].filter(draftDirty)
1568
+ });
1569
+ try {
1570
+ if (!storage || record.length > maxStoredCharacters) throw new Error("Storage unavailable");
1571
+ storage.setItem(key, record);
1572
+ warning = "";
1573
+ return true;
1574
+ } catch {
1575
+ warning = "This draft is kept in this tab only. Browser storage is unavailable or full; save or download it before closing the tab.";
1576
+ return false;
1577
+ }
1578
+ }
1579
+ function put(draft) {
1580
+ if (encoder.encode(draft.contents).length > 2097152 || !draft.contents.isWellFormed() || draft.contents.includes("\0")) throw new Error("This draft exceeds the text editor limit or contains unsupported text. Undo the last edit before saving.");
1581
+ let clean = [...drafts.values()].filter((item) => item.path !== draft.path && !draftDirty(item));
1582
+ for (let item of clean) drafts.delete(item.path);
1583
+ let others = [...drafts.values()].filter((item) => item.path !== draft.path);
1584
+ if (others.length >= maxDrafts || others.reduce((sum, item) => sum + bytes(item), bytes(draft)) > maxDraftBytes) throw new Error("Save or discard another draft before editing more files. Your existing drafts have been kept.");
1585
+ drafts.set(draft.path, draft);
1586
+ persist();
1587
+ return draft;
1588
+ }
1589
+ return {
1590
+ get warning() {
1591
+ return warning;
1592
+ },
1593
+ get dirty() {
1594
+ return [...drafts.values()].filter(draftDirty);
1595
+ },
1596
+ get(path) {
1597
+ return drafts.get(path);
1598
+ },
1599
+ open(path, revision, contents) {
1600
+ let previous = drafts.get(path);
1601
+ if (previous && draftDirty(previous)) return previous;
1602
+ parseFileSaveRequest({
1603
+ path,
1604
+ expectedRevision: revision,
1605
+ contents
1606
+ });
1607
+ return put({
1608
+ id: crypto.randomUUID(),
1609
+ path,
1610
+ baseRevision: revision,
1611
+ baseContents: contents,
1612
+ contents,
1613
+ updatedAt: now()
1614
+ });
1615
+ },
1616
+ change(path, contents) {
1617
+ let draft = drafts.get(path);
1618
+ if (!draft) throw new Error("This draft is no longer open.");
1619
+ return put({
1620
+ ...draft,
1621
+ contents,
1622
+ updatedAt: now()
1623
+ });
1624
+ },
1625
+ acknowledge(path, savedContents, revision) {
1626
+ let draft = drafts.get(path);
1627
+ if (!draft) return;
1628
+ return put({
1629
+ ...draft,
1630
+ baseRevision: revision,
1631
+ baseContents: savedContents,
1632
+ updatedAt: now()
1633
+ });
1634
+ },
1635
+ rebase(path, revision, contents) {
1636
+ let draft = drafts.get(path);
1637
+ if (!draft) throw new Error("This draft is no longer open.");
1638
+ return put({
1639
+ ...draft,
1640
+ id: crypto.randomUUID(),
1641
+ baseRevision: revision,
1642
+ baseContents: contents,
1643
+ updatedAt: now()
1644
+ });
1645
+ },
1646
+ discard(path) {
1647
+ drafts.delete(path);
1648
+ persist();
1649
+ },
1650
+ persist,
1651
+ beforeMutation(intent, plan) {
1652
+ let dirty = [...drafts.values()].filter(draftDirty);
1653
+ if (intent.kind === "delete" && dirty.some((draft) => intent.paths.some((path) => within(draft.path, path)))) return "Save or discard the unsaved drafts in this selection before deleting it.";
1654
+ if (intent.kind === "restore" && dirty.some((draft) => plan.items.some((item) => item.destination && within(draft.path, item.destination)))) return "Save or discard the unsaved drafts at these original paths before restoring them.";
1655
+ if (intent.kind === "move") {
1656
+ if (dirty.some((draft) => intent.entries.some((entry) => within(draft.path, entry.destination) && !within(draft.path, entry.path)))) return "A destination has an unsaved draft. Save or discard it before moving files here.";
1657
+ if (dirty.some((draft) => intent.entries.some((entry) => within(draft.path, entry.path))) && !persist()) return "Save or download your drafts before moving these files; local draft storage is unavailable.";
1658
+ }
1659
+ return null;
1660
+ },
1661
+ afterMutation(intent, result) {
1662
+ if (intent.kind !== "move") return;
1663
+ let changes = result.items.filter((item) => item.status === "applied" && item.source && item.destination);
1664
+ let moved = [];
1665
+ let next = new Map(drafts);
1666
+ for (let draft of drafts.values()) {
1667
+ let item = changes.find((item) => within(draft.path, item.source));
1668
+ if (!item) continue;
1669
+ let path = parseFilePath(item.destination + draft.path.slice(item.source.length));
1670
+ moved.push({
1671
+ draft,
1672
+ path
1673
+ });
1674
+ next.delete(draft.path);
1675
+ }
1676
+ for (let { draft, path } of moved) {
1677
+ let destination = next.get(path);
1678
+ if (destination && draftDirty(destination)) throw new Error("The destination acquired an unsaved draft while the move was running. Both drafts remain available at their original paths; download or review them before continuing.");
1679
+ next.set(path, {
1680
+ ...draft,
1681
+ id: crypto.randomUUID(),
1682
+ path
1683
+ });
1684
+ }
1685
+ drafts = next;
1686
+ persist();
1687
+ }
1688
+ };
1689
+ }
1690
+ var openProjects = /* @__PURE__ */ new Map();
1691
+ var unloadWarningInstalled = false;
1692
+ /**
1693
+ * Mount-time only: retain unsaved data through client route replacement, including storage
1694
+ * failures.
1695
+ * @param projectId - Connected project identity.
1696
+ * @param storage - Browser persistence for this tab.
1697
+ * @returns The bounded shared draft owner for the project.
1698
+ */
1699
+ function projectFileDrafts(projectId, storage) {
1700
+ if (!unloadWarningInstalled) {
1701
+ unloadWarningInstalled = true;
1702
+ window.addEventListener("beforeunload", (event) => {
1703
+ if ([...openProjects.values()].some((drafts) => drafts.dirty.length)) {
1704
+ for (let drafts of openProjects.values()) drafts.persist();
1705
+ event.preventDefault();
1706
+ event.returnValue = "";
1707
+ }
1708
+ });
1709
+ }
1710
+ let existing = openProjects.get(projectId);
1711
+ if (existing) return existing;
1712
+ for (let [id, drafts] of openProjects) if (!drafts.dirty.length) openProjects.delete(id);
1713
+ if (openProjects.size >= 4) throw new Error("Save or discard drafts in another project before opening more editors.");
1714
+ let drafts = createFileDrafts(projectId, storage);
1715
+ openProjects.set(projectId, drafts);
1716
+ return drafts;
1717
+ }
1718
+ //#endregion
1719
+ //#region app/client/file-editor-controller.ts
1720
+ function message$3(cause) {
1721
+ return cause instanceof Error ? cause.message : "The file could not be saved. Your draft has been kept.";
1722
+ }
1723
+ function readable(document, path) {
1724
+ if (document.entry.path !== path) throw new Error("The source response belongs to another file.");
1725
+ if (document.contents === null || !document.revision) throw new Error("Only regular UTF-8 text files within the editor size limit can be edited.");
1726
+ return {
1727
+ revision: document.revision,
1728
+ contents: document.contents
1729
+ };
1730
+ }
1731
+ function inside(path, parent) {
1732
+ return path === parent || path.startsWith(`${parent}/`);
1733
+ }
1734
+ /**
1735
+ * Keeps acknowledged bytes separate from live drafts, including edits made during a save.
1736
+ * @param options - Scoped transport, draft owner and application notifications.
1737
+ * @returns Explicit editor, save and conflict commands.
1738
+ */
1739
+ function createFileEditorController(options) {
1740
+ let state = null;
1741
+ let opening = false;
1742
+ let requestedPath = null;
1743
+ let openError = "";
1744
+ let request;
1745
+ let saves = /* @__PURE__ */ new Set();
1746
+ let generation = 0;
1747
+ function changed() {
1748
+ if (!options.signal.aborted) options.onChange();
1749
+ }
1750
+ function close() {
1751
+ request?.abort();
1752
+ generation++;
1753
+ state = null;
1754
+ requestedPath = null;
1755
+ opening = false;
1756
+ openError = "";
1757
+ options.drafts.persist();
1758
+ changed();
1759
+ }
1760
+ function closeOtherPath(path) {
1761
+ if (requestedPath !== path) close();
1762
+ }
1763
+ function observe(document) {
1764
+ if (!state || state.draft.path !== document.entry.path || state.saving || state.latest) return;
1765
+ if (document.revision !== state.draft.baseRevision) {
1766
+ state = {
1767
+ ...state,
1768
+ conflict: true,
1769
+ error: "This file changed outside the editor. Your draft has been kept. Compare with the latest file before saving."
1770
+ };
1771
+ changed();
1772
+ }
1773
+ }
1774
+ async function begin(path, mode = "file", baseline) {
1775
+ request?.abort();
1776
+ let controller = new AbortController();
1777
+ request = controller;
1778
+ let signal = AbortSignal.any([controller.signal, options.signal]);
1779
+ let turn = ++generation;
1780
+ requestedPath = path;
1781
+ opening = true;
1782
+ openError = "";
1783
+ changed();
1784
+ try {
1785
+ let document = await options.read(path, signal);
1786
+ signal.throwIfAborted();
1787
+ let current = readable(document, path);
1788
+ let draft = options.drafts.open(path, current.revision, current.contents);
1789
+ let conflict = draft.baseRevision !== current.revision;
1790
+ let useIndex = mode === "diff" && baseline?.workingRevision === current.revision;
1791
+ state = {
1792
+ key: `${draft.id}:${turn}`,
1793
+ draft,
1794
+ file: {
1795
+ name: path,
1796
+ contents: draft.contents
1797
+ },
1798
+ oldFile: useIndex ? baseline.file : {
1799
+ name: path,
1800
+ contents: draft.baseContents
1801
+ },
1802
+ mode,
1803
+ baselineLabel: useIndex ? "Index" : "Opened version",
1804
+ saving: saves.has(path),
1805
+ conflict,
1806
+ latest: null,
1807
+ error: conflict ? "This recovered draft belongs to an older file version. Compare it with the latest file before saving." : "",
1808
+ status: draftDirty(draft) ? "Recovered unsaved draft." : ""
1809
+ };
1810
+ } catch (cause) {
1811
+ if (!signal.aborted) {
1812
+ let retained = options.drafts.get(path);
1813
+ if (retained && draftDirty(retained)) state = {
1814
+ key: `${retained.id}:recovery:${turn}`,
1815
+ draft: retained,
1816
+ file: {
1817
+ name: path,
1818
+ contents: retained.contents
1819
+ },
1820
+ oldFile: {
1821
+ name: path,
1822
+ contents: retained.baseContents
1823
+ },
1824
+ mode: "file",
1825
+ baselineLabel: "Opened version",
1826
+ saving: saves.has(path),
1827
+ conflict: true,
1828
+ latest: null,
1829
+ error: `The current project file could not be read. ${message$3(cause)}`,
1830
+ status: "Your unsaved draft is available below. Download it, or restore the project file and compare with its latest contents before saving."
1831
+ };
1832
+ else openError = message$3(cause);
1833
+ }
1834
+ } finally {
1835
+ if (turn === generation) {
1836
+ opening = false;
1837
+ changed();
1838
+ }
1839
+ }
1840
+ }
1841
+ function change(contents) {
1842
+ if (!state) return;
1843
+ try {
1844
+ let draft = options.drafts.change(state.draft.path, contents);
1845
+ state = {
1846
+ ...state,
1847
+ draft,
1848
+ status: "",
1849
+ error: state.conflict ? state.error : ""
1850
+ };
1851
+ } catch (cause) {
1852
+ state = {
1853
+ ...state,
1854
+ error: message$3(cause)
1855
+ };
1856
+ }
1857
+ changed();
1858
+ }
1859
+ async function save() {
1860
+ if (!state || state.saving || state.conflict || state.latest || !draftDirty(state.draft)) return;
1861
+ let draft = state.draft;
1862
+ saves.add(draft.path);
1863
+ state = {
1864
+ ...state,
1865
+ saving: true,
1866
+ error: "",
1867
+ status: ""
1868
+ };
1869
+ changed();
1870
+ try {
1871
+ let result = await options.save({
1872
+ path: draft.path,
1873
+ expectedRevision: draft.baseRevision,
1874
+ contents: draft.contents
1875
+ }, options.signal);
1876
+ if (result.path !== draft.path || result.previousRevision !== draft.baseRevision) throw new Error("The save response belongs to a different file revision. Check the latest file before retrying.");
1877
+ let next = options.drafts.acknowledge(draft.path, draft.contents, result.revision);
1878
+ if (state?.draft.path === draft.path && next) {
1879
+ state = {
1880
+ ...state,
1881
+ draft: next,
1882
+ status: "",
1883
+ error: ""
1884
+ };
1885
+ if (result.refresh === "failed") toast.error("Saved, but the file browser could not refresh", { description: "Refresh it before continuing." });
1886
+ else if (draftDirty(next)) toast.success("Saved to your project", { description: "Newer edits are still unsaved." });
1887
+ else toast.success("Saved to your project");
1888
+ }
1889
+ options.onSaved();
1890
+ } catch (cause) {
1891
+ if (state?.draft.path === draft.path) state = {
1892
+ ...state,
1893
+ error: message$3(cause),
1894
+ conflict: true
1895
+ };
1896
+ } finally {
1897
+ saves.delete(draft.path);
1898
+ if (state?.draft.path === draft.path) state = {
1899
+ ...state,
1900
+ saving: false
1901
+ };
1902
+ changed();
1903
+ }
1904
+ }
1905
+ async function checkLatest() {
1906
+ if (!state || state.saving) return;
1907
+ let previous = state;
1908
+ request?.abort();
1909
+ let controller = new AbortController();
1910
+ request = controller;
1911
+ let signal = AbortSignal.any([controller.signal, options.signal]);
1912
+ let turn = ++generation;
1913
+ try {
1914
+ let document = await options.read(previous.draft.path, signal);
1915
+ signal.throwIfAborted();
1916
+ let current = readable(document, previous.draft.path);
1917
+ if (!state || state.draft.path !== previous.draft.path) return;
1918
+ let draft = options.drafts.get(previous.draft.path);
1919
+ state = {
1920
+ ...state,
1921
+ key: `${draft.id}:merge:${turn}`,
1922
+ file: {
1923
+ name: draft.path,
1924
+ contents: draft.contents
1925
+ },
1926
+ oldFile: {
1927
+ name: draft.path,
1928
+ contents: current.contents
1929
+ },
1930
+ mode: "diff",
1931
+ baselineLabel: "Latest file",
1932
+ latest: document,
1933
+ conflict: true,
1934
+ error: "",
1935
+ status: "Your draft is on the new side. No file is changed until you review it and save."
1936
+ };
1937
+ } catch (cause) {
1938
+ if (!signal.aborted && state) state = {
1939
+ ...state,
1940
+ error: message$3(cause)
1941
+ };
1942
+ } finally {
1943
+ changed();
1944
+ }
1945
+ }
1946
+ function acceptMerge() {
1947
+ if (!state?.latest) return;
1948
+ let latest = readable(state.latest, state.draft.path);
1949
+ let draft = options.drafts.rebase(state.draft.path, latest.revision, latest.contents);
1950
+ state = {
1951
+ ...state,
1952
+ draft,
1953
+ latest: null,
1954
+ conflict: false,
1955
+ error: "",
1956
+ status: "Reviewed against the latest file. Save when your merged draft is ready."
1957
+ };
1958
+ changed();
1959
+ }
1960
+ async function discard() {
1961
+ if (!state || state.saving) return;
1962
+ let path = state.draft.path;
1963
+ options.drafts.discard(path);
1964
+ close();
1965
+ await begin(path);
1966
+ }
1967
+ return {
1968
+ get state() {
1969
+ return state;
1970
+ },
1971
+ get opening() {
1972
+ return opening;
1973
+ },
1974
+ get openError() {
1975
+ return openError;
1976
+ },
1977
+ begin,
1978
+ change,
1979
+ save,
1980
+ close,
1981
+ closeOtherPath,
1982
+ observe,
1983
+ checkLatest,
1984
+ acceptMerge,
1985
+ discard,
1986
+ guard: {
1987
+ async beforeApply(intent, plan, signal) {
1988
+ signal.throwIfAborted();
1989
+ if ([...saves].some((path) => plan.items.some((item) => item.source && inside(path, item.source) || item.destination && inside(path, item.destination)))) return "Wait for the file save to finish before changing these paths.";
1990
+ return options.drafts.beforeMutation(intent, plan);
1991
+ },
1992
+ afterApply(intent, result) {
1993
+ options.drafts.afterMutation(intent, result);
1994
+ if (state && intent.kind === "move" && result.items.some((item) => item.status === "applied" && item.source && inside(state.draft.path, item.source))) close();
1995
+ changed();
1996
+ }
1997
+ }
1998
+ };
1999
+ }
2000
+ //#endregion
2001
+ //#region app/client/file-review-actions-controller.ts
2002
+ function message$2(cause) {
2003
+ return cause instanceof Error ? cause.message : "The review action could not be completed. Refresh and inspect retained undo before trying again.";
2004
+ }
2005
+ /**
2006
+ * Runs review actions, such as staging or discarding changes, one at a time: plans one, applies
2007
+ * it and lists recoveries, dropping the answers of superseded requests.
2008
+ * @param options - The component's lifetime, the Files API calls and the callbacks.
2009
+ * @returns The controller and its dialog state.
2010
+ */
2011
+ function createFileReviewActionsController(options) {
2012
+ let state = null, request, generation = 0;
2013
+ function changed() {
2014
+ if (!options.signal.aborted) options.onChange();
2015
+ }
2016
+ function start(phase) {
2017
+ request?.abort();
2018
+ request = new AbortController();
2019
+ generation++;
2020
+ state = {
2021
+ phase,
2022
+ plan: null,
2023
+ result: null,
2024
+ recoveries: null,
2025
+ error: ""
2026
+ };
2027
+ changed();
2028
+ return {
2029
+ signal: AbortSignal.any([options.signal, request.signal]),
2030
+ turn: generation
2031
+ };
2032
+ }
2033
+ function close() {
2034
+ if (state?.phase === "applying") return;
2035
+ request?.abort();
2036
+ generation++;
2037
+ state = null;
2038
+ changed();
2039
+ }
2040
+ async function prepare(intent) {
2041
+ if (state?.phase === "applying") return;
2042
+ let { signal, turn } = start("preparing");
2043
+ try {
2044
+ if (intent.kind !== "undo") options.guard([intent.path], intent.kind === "discard" ? "worktree" : "index");
2045
+ let plan = await options.prepare(intent, signal);
2046
+ signal.throwIfAborted();
2047
+ if (plan.kind !== intent.kind || intent.kind !== "undo" && !plan.paths.includes(intent.path)) throw new Error("The prepared review belongs to a different action or file.");
2048
+ options.guard(plan.paths, plan.target);
2049
+ if (turn === generation) state = {
2050
+ phase: "ready",
2051
+ plan,
2052
+ result: null,
2053
+ recoveries: null,
2054
+ error: ""
2055
+ };
2056
+ } catch (cause) {
2057
+ if (!signal.aborted && turn === generation) state = {
2058
+ phase: "error",
2059
+ plan: null,
2060
+ result: null,
2061
+ recoveries: null,
2062
+ error: message$2(cause)
2063
+ };
2064
+ } finally {
2065
+ if (turn === generation) changed();
2066
+ }
2067
+ }
2068
+ async function apply() {
2069
+ if (state?.phase !== "ready" || !state.plan) return;
2070
+ let plan = state.plan, turn = generation, signal = AbortSignal.any([options.signal, request.signal]);
2071
+ try {
2072
+ options.guard(plan.paths, plan.target);
2073
+ state = {
2074
+ ...state,
2075
+ phase: "applying",
2076
+ error: ""
2077
+ };
2078
+ changed();
2079
+ let result = await options.apply({
2080
+ id: plan.id,
2081
+ revision: plan.revision
2082
+ }, signal);
2083
+ signal.throwIfAborted();
2084
+ if (result.id !== plan.id || result.target !== plan.target || result.changedPaths.some((path) => !plan.paths.includes(path))) throw new Error("The write response could not be confirmed. Refresh and inspect retained undo.");
2085
+ if (turn === generation) {
2086
+ let refreshError = "";
2087
+ try {
2088
+ await options.onApplied(result);
2089
+ } catch (cause) {
2090
+ refreshError = message$2(cause);
2091
+ }
2092
+ signal.throwIfAborted();
2093
+ if (turn === generation) state = {
2094
+ ...state,
2095
+ phase: "done",
2096
+ result,
2097
+ error: refreshError
2098
+ };
2099
+ }
2100
+ } catch (cause) {
2101
+ if (!signal.aborted && turn === generation && state) state = {
2102
+ ...state,
2103
+ phase: "error",
2104
+ error: message$2(cause)
2105
+ };
2106
+ } finally {
2107
+ if (turn === generation) changed();
2108
+ }
2109
+ }
2110
+ async function recoveries() {
2111
+ if (state?.phase === "applying") return;
2112
+ let { signal, turn } = start("preparing");
2113
+ try {
2114
+ let items = await options.recoveries(signal);
2115
+ if (!signal.aborted && turn === generation) state = {
2116
+ phase: "recoveries",
2117
+ plan: null,
2118
+ result: null,
2119
+ recoveries: items,
2120
+ error: ""
2121
+ };
2122
+ } catch (cause) {
2123
+ if (!signal.aborted && turn === generation) state = {
2124
+ phase: "error",
2125
+ plan: null,
2126
+ result: null,
2127
+ recoveries: null,
2128
+ error: message$2(cause)
2129
+ };
2130
+ } finally {
2131
+ if (turn === generation) changed();
2132
+ }
2133
+ }
2134
+ options.signal.addEventListener("abort", () => {
2135
+ request?.abort();
2136
+ state = null;
2137
+ }, { once: true });
2138
+ return {
2139
+ get state() {
2140
+ return state;
2141
+ },
2142
+ prepare,
2143
+ apply,
2144
+ recoveries,
2145
+ close
2146
+ };
2147
+ }
2148
+ //#endregion
2149
+ //#region app/client/project-code-comments.ts
2150
+ /**
2151
+ * Where a code conversation's lines are in the review, for selecting them.
2152
+ * @param location - The conversation's anchor as it resolves in the current source.
2153
+ * @returns The lines and their revision, or null when the anchor is outdated.
2154
+ */
2155
+ function codeConversationPosition(location) {
2156
+ if (location.resolution.state === "outdated") return null;
2157
+ return {
2158
+ selection: {
2159
+ path: location.source.viewPath ?? location.source.path,
2160
+ start: location.resolution.startLine,
2161
+ end: location.resolution.endLine,
2162
+ ...location.source.side ? { side: location.source.side === "old" ? "deletions" : "additions" } : {}
2163
+ },
2164
+ revision: location.resolution.revision
2165
+ };
2166
+ }
2167
+ /**
2168
+ * The sources a review can be commented on: the shown source, the open file, or both sides of
2169
+ * every diff.
2170
+ * @param document - The open file, if any.
2171
+ * @param diffs - The shown diffs.
2172
+ * @param source - The shown source at a commit, if any.
2173
+ * @returns The commentable sources.
2174
+ */
2175
+ function reviewCommentSources(document, diffs, source) {
2176
+ if (source) return [{
2177
+ path: source.path,
2178
+ revision: source.file?.revision ?? null,
2179
+ contents: source.file?.contents ?? null,
2180
+ commit: source.commit
2181
+ }];
2182
+ if (document) return [{
2183
+ path: document.entry.path,
2184
+ revision: document.revision,
2185
+ contents: document.contents,
2186
+ commit: null
2187
+ }];
2188
+ return diffs.flatMap((diff) => [{
2189
+ path: diff.oldFile?.name ?? diff.entry.originalPath ?? diff.entry.path,
2190
+ revision: diff.oldFile?.revision ?? null,
2191
+ contents: diff.oldFile?.contents ?? null,
2192
+ viewPath: diff.entry.path,
2193
+ side: "old",
2194
+ commit: diff.base
2195
+ }, {
2196
+ path: diff.entry.path,
2197
+ previousPath: diff.entry.originalPath,
2198
+ revision: diff.newFile?.revision ?? null,
2199
+ contents: diff.newFile?.contents ?? null,
2200
+ viewPath: diff.entry.path,
2201
+ side: "new",
2202
+ commit: diff.target
2203
+ }]);
2204
+ }
2205
+ /**
2206
+ * Anchors selected lines to their exact source revision for a new code comment.
2207
+ * @param selection - Consecutive lines on one side.
2208
+ * @param sources - The commentable sources.
2209
+ * @param expectedRevision - The revision the selection was made in, when known.
2210
+ * @returns The code anchor.
2211
+ */
2212
+ function captureCodeSelection(selection, sources, expectedRevision) {
2213
+ if (selection.endSide && selection.endSide !== selection.side) throw new Error("Select consecutive lines on one side before commenting.");
2214
+ let side = selection.side === "deletions" ? "old" : "new";
2215
+ let source = sources.find((source) => (source.viewPath ?? source.path) === selection.path && (!source.side || source.side === side));
2216
+ if (!source || source.contents === null || !source.revision) throw new Error("Comments require readable text with an exact source revision.");
2217
+ if (expectedRevision && source.revision !== expectedRevision) throw new Error("Those lines belong to an older source. Select them again before commenting.");
2218
+ return createCodeAnchor({
2219
+ path: source.path,
2220
+ revision: source.revision,
2221
+ commit: source.commit ?? null,
2222
+ side,
2223
+ startLine: Math.min(selection.start, selection.end),
2224
+ endLine: Math.max(selection.start, selection.end)
2225
+ }, source.contents);
2226
+ }
2227
+ //#endregion
2228
+ //#region app/client/file-search-controller.ts
2229
+ /**
2230
+ * Writes a search query as the search API's URL parameters.
2231
+ * @param query - The validated query.
2232
+ * @returns The parameters, with one `include` and `exclude` entry for each pattern.
2233
+ */
2234
+ function fileSearchParameters(query) {
2235
+ let params = new URLSearchParams({
2236
+ query: query.query,
2237
+ regex: String(query.regex),
2238
+ caseSensitive: String(query.caseSensitive),
2239
+ wholeWord: String(query.wholeWord),
2240
+ directory: query.directory,
2241
+ includeDotfiles: String(query.includeDotfiles),
2242
+ includeIgnored: String(query.includeIgnored),
2243
+ limit: String(query.limit)
2244
+ });
2245
+ for (let value of query.include) params.append("include", value);
2246
+ for (let value of query.exclude) params.append("exclude", value);
2247
+ return params;
2248
+ }
2249
+ /**
2250
+ * Superseded and cancelled requests never publish their result or error.
2251
+ * @param options - Component lifetime, validated transport and render notification.
2252
+ * @returns A single request owner and its observable search state.
2253
+ */
2254
+ function createFileSearchController(options) {
2255
+ let active;
2256
+ let state = {
2257
+ loading: false,
2258
+ result: null,
2259
+ error: "",
2260
+ notice: ""
2261
+ };
2262
+ function notify() {
2263
+ if (!options.signal.aborted) options.onChange();
2264
+ }
2265
+ function cancel(notice = "Search cancelled.") {
2266
+ active?.abort();
2267
+ active = void 0;
2268
+ state = {
2269
+ ...state,
2270
+ loading: false,
2271
+ notice,
2272
+ error: ""
2273
+ };
2274
+ notify();
2275
+ }
2276
+ options.signal.addEventListener("abort", () => cancel(""), { once: true });
2277
+ return {
2278
+ get state() {
2279
+ return state;
2280
+ },
2281
+ cancel,
2282
+ async search(input) {
2283
+ cancel("");
2284
+ let owner = new AbortController();
2285
+ active = owner;
2286
+ let signal = AbortSignal.any([owner.signal, options.signal]);
2287
+ state = {
2288
+ loading: true,
2289
+ result: null,
2290
+ error: "",
2291
+ notice: ""
2292
+ };
2293
+ notify();
2294
+ try {
2295
+ let query = parseFileSearchQuery(input);
2296
+ let result = await options.request(query, signal);
2297
+ if (fileSearchParameters(result.query).toString() !== fileSearchParameters(query).toString()) throw new FilesRequestError("The service returned results for a different search.", "response");
2298
+ if (signal.aborted || active !== owner) return;
2299
+ state = {
2300
+ loading: false,
2301
+ result,
2302
+ error: "",
2303
+ notice: ""
2304
+ };
2305
+ } catch (cause) {
2306
+ if (signal.aborted || active !== owner) return;
2307
+ state = {
2308
+ loading: false,
2309
+ result: null,
2310
+ error: cause instanceof Error ? cause.message : "Could not search this project.",
2311
+ notice: ""
2312
+ };
2313
+ } finally {
2314
+ if (active === owner) {
2315
+ active = void 0;
2316
+ notify();
2317
+ }
2318
+ owner.abort();
2319
+ }
2320
+ }
2321
+ };
2322
+ }
2323
+ /**
2324
+ * Search offsets only navigate the exact on-disk version that produced them.
2325
+ * @param document - Current complete file response.
2326
+ * @param match - Captured search result with revision and coordinates.
2327
+ * @returns A verified source-review position, or an explicit outdated-result error.
2328
+ */
2329
+ function searchMatchPosition(document, match) {
2330
+ if (document.entry.path !== match.path || document.revision !== match.revision || document.entry.content !== "text" || typeof document.contents !== "string" || match.start < 0 || match.end < match.start || match.end > document.contents.length) throw new Error("This search result is outdated. Search again to locate the current text; any unsaved draft is retained.");
2331
+ let position = (offset) => {
2332
+ let line = 1, start = 0;
2333
+ for (let ending of document.contents.matchAll(/\r\n|\r|\n/g)) {
2334
+ let next = ending.index + ending[0].length;
2335
+ if (next > offset) break;
2336
+ line++;
2337
+ start = next;
2338
+ }
2339
+ return {
2340
+ line,
2341
+ column: offset - start + 1
2342
+ };
2343
+ };
2344
+ let before = position(match.start), end = position(match.end);
2345
+ if (before.line !== match.startLine || before.column !== match.startColumn || end.line !== match.endLine || end.column !== match.endColumn) throw new Error("This search result has invalid coordinates. Search again.");
2346
+ return {
2347
+ page: 0,
2348
+ scroll: 0,
2349
+ revision: match.revision,
2350
+ selection: {
2351
+ path: match.path,
2352
+ start: match.startLine,
2353
+ end: Math.max(match.startLine, match.endLine - Number(match.endColumn === 1 && match.endLine > match.startLine))
2354
+ }
2355
+ };
2356
+ }
2357
+ //#endregion
2358
+ //#region app/client/project-file-review.tsx
2359
+ function message$1(cause) {
2360
+ return cause instanceof Error ? cause.message : "Could not read the project.";
2361
+ }
2362
+ /**
2363
+ * Owns validated review requests, the worker pool and bounded project view persistence.
2364
+ * @param handle - Selected path and the connected project's public capability.
2365
+ * @returns The review render function.
2366
+ */
2367
+ function ProjectFileReview(handle) {
2368
+ let project = handle.props.project;
2369
+ let session = {
2370
+ version: 1,
2371
+ projectId: project.id,
2372
+ view: defaultReviewView(),
2373
+ positions: {}
2374
+ };
2375
+ let document = null;
2376
+ let snapshot = null;
2377
+ let diffs = [];
2378
+ let failures = {};
2379
+ let error = "";
2380
+ let loading = false;
2381
+ let history = null;
2382
+ let historyOpen = false;
2383
+ let historyLoading = false;
2384
+ let historyError = "";
2385
+ let theme = "light";
2386
+ let pool;
2387
+ let poolSettled = false;
2388
+ let request;
2389
+ let historyRequest;
2390
+ let persistTimer;
2391
+ let mounted = false;
2392
+ let renderedHref = "";
2393
+ let renderedConversation;
2394
+ let pendingHref;
2395
+ let lastKey = "";
2396
+ let displayedKey = "";
2397
+ let historyKey = "";
2398
+ let refresh = handle.props.refresh;
2399
+ let appliedHighlights = "";
2400
+ let drafts;
2401
+ let editor;
2402
+ let preferences = defaultEditorPreferences();
2403
+ let observedPath = handle.props.path;
2404
+ let actions;
2405
+ let merge;
2406
+ let historical = null;
2407
+ let source = null;
2408
+ let panelOpen = false, selectedThread, draftAnchor;
2409
+ let commentError = "", requestedThread = null, appliedThread = null;
2410
+ let commentSources = [], commentLocations = [];
2411
+ let sourceSignature = "", locationsVersion;
2412
+ let pendingFocus = null;
2413
+ let editorLocations;
2414
+ let locatedEditor;
2415
+ let searchSelectionId = 0, searchNotice = "";
2416
+ let pendingSearch;
2417
+ let commentReturnFocus;
2418
+ let host;
2419
+ let reportedDrafts = "";
2420
+ let reportedComments = false;
2421
+ let store = handle.props.store;
2422
+ if (!store) projectStore(project).then((opened) => {
2423
+ store = opened;
2424
+ });
2425
+ function openComments() {
2426
+ if (!panelOpen) {
2427
+ let active = window.document.activeElement;
2428
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
2429
+ commentReturnFocus = active instanceof HTMLElement ? active : void 0;
2430
+ }
2431
+ panelOpen = true;
2432
+ }
2433
+ function closeComments() {
2434
+ panelOpen = false;
2435
+ draftAnchor = void 0;
2436
+ changed();
2437
+ handle.queueTask(() => {
2438
+ (commentReturnFocus?.isConnected ? commentReturnFocus : host?.querySelector("diffs-container")?.shadowRoot?.querySelector("[contenteditable=true]") ?? host?.querySelector("[data-bud-code-review]"))?.focus({ preventScroll: true });
2439
+ commentReturnFocus = void 0;
2440
+ });
2441
+ }
2442
+ function changed() {
2443
+ if (!handle.signal.aborted) handle.update();
2444
+ }
2445
+ function context() {
2446
+ return historical ? JSON.stringify([reviewContextKey(session.view, handle.props.path), historical.commit]) : reviewContextKey(session.view, handle.props.path);
2447
+ }
2448
+ function position() {
2449
+ return session.positions[context()] ?? defaultReviewPosition();
2450
+ }
2451
+ function persist() {
2452
+ clearTimeout(persistTimer);
2453
+ persistTimer = setTimeout(() => writePreference(`bud:review:${project.id}:v1`, JSON.stringify(session)), 200);
2454
+ }
2455
+ function savePosition(change, render = false) {
2456
+ let previous = position();
2457
+ let next = {
2458
+ ...previous,
2459
+ ...change
2460
+ };
2461
+ session = rememberReviewPosition(session, context(), next);
2462
+ persist();
2463
+ if (render || JSON.stringify(previous.selection) !== JSON.stringify(next.selection) || previous.revision !== next.revision) changed();
2464
+ }
2465
+ function location() {
2466
+ let href = reviewHref(handle.props.path, session.view);
2467
+ if (window.location.pathname + window.location.search + window.location.hash !== href) {
2468
+ pendingHref = new URL(href, window.location.origin).href;
2469
+ window.history.pushState({ ...window.history.state }, "", href);
2470
+ }
2471
+ }
2472
+ function changeView(change) {
2473
+ historical = null;
2474
+ source = null;
2475
+ session = {
2476
+ ...session,
2477
+ view: {
2478
+ ...session.view,
2479
+ ...change
2480
+ }
2481
+ };
2482
+ persist();
2483
+ location();
2484
+ changed();
2485
+ }
2486
+ let reading;
2487
+ async function load() {
2488
+ let finishRead;
2489
+ reading = new Promise((resolve) => {
2490
+ finishRead = resolve;
2491
+ });
2492
+ request?.abort();
2493
+ let controller = new AbortController();
2494
+ request = controller;
2495
+ let signal = AbortSignal.any([handle.signal, controller.signal]);
2496
+ let selected = handle.props.path;
2497
+ let settings = session.view;
2498
+ let immutable = historical && historical.path === selected ? historical : null;
2499
+ let key = context();
2500
+ let nextDisplay = JSON.stringify([
2501
+ key,
2502
+ position().page,
2503
+ selected
2504
+ ]);
2505
+ let comparison = reviewComparison(settings);
2506
+ loading = true;
2507
+ error = "";
2508
+ if (displayedKey !== nextDisplay) {
2509
+ document = null;
2510
+ snapshot = null;
2511
+ diffs = [];
2512
+ source = null;
2513
+ failures = {};
2514
+ }
2515
+ changed();
2516
+ try {
2517
+ if (immutable) {
2518
+ let next = await requestFiles(`${routes.fileReviewApi.source.href()}?${new URLSearchParams(immutable)}`, project.token, signal, parseReviewSource);
2519
+ if (next.path !== immutable.path || next.commit !== immutable.commit) throw new FilesRequestError("The source response belongs to another commit or file.", "response");
2520
+ if (!signal.aborted) {
2521
+ source = next;
2522
+ displayedKey = nextDisplay;
2523
+ }
2524
+ } else if (!comparison) {
2525
+ if (selected) {
2526
+ let stored = displayedKey !== nextDisplay && store ? storedDocument(store, selected) : null;
2527
+ if (stored) {
2528
+ document = stored;
2529
+ displayedKey = nextDisplay;
2530
+ loading = false;
2531
+ changed();
2532
+ }
2533
+ let next = await requestFiles(`${routes.filesApi.read.href()}?${new URLSearchParams({ path: selected })}`, project.token, signal, parseFileDocument);
2534
+ if (next.entry.path !== selected) throw new FilesRequestError("The source response belongs to another file.", "response");
2535
+ if (store) storeDocument(store, next).catch(() => {});
2536
+ if (!signal.aborted) {
2537
+ document = document?.revision === next.revision && document.entry.path === next.entry.path ? document : next;
2538
+ displayedKey = nextDisplay;
2539
+ editor?.observe(next);
2540
+ if (pendingSearch?.path === selected) {
2541
+ let match = pendingSearch;
2542
+ pendingSearch = void 0;
2543
+ try {
2544
+ savePosition(searchMatchPosition(next, match), true);
2545
+ searchNotice = `Search match at ${match.startLine}:${match.startColumn}. Any unsaved draft is retained.`;
2546
+ } catch (cause) {
2547
+ searchNotice = message$1(cause);
2548
+ }
2549
+ }
2550
+ }
2551
+ }
2552
+ } else for (let attempt = 0; attempt < 2; attempt++) {
2553
+ let next = await requestFiles(`${routes.fileReviewApi.compare.href()}?${comparisonQuery(comparison)}`, project.token, signal, parseReviewSnapshot);
2554
+ if (next.comparison.mode !== comparison.mode) throw new FilesRequestError("The service returned a different comparison.", "response");
2555
+ let page = Math.min(position().page, Math.max(0, Math.ceil(next.entries.length / 20) - 1));
2556
+ let restoredPath = position().selection?.path;
2557
+ if (restoredPath && settings.multiple && next.entries.some((entry) => entry.path === restoredPath)) page = Math.floor(next.entries.findIndex((entry) => entry.path === restoredPath) / 20);
2558
+ if (page !== position().page) savePosition({
2559
+ ...position(),
2560
+ page
2561
+ });
2562
+ let paths = settings.multiple ? next.entries.slice(page * 20, (page + 1) * 20).map((entry) => entry.path) : next.entries.filter((entry) => entry.path === selected).map((entry) => entry.path);
2563
+ try {
2564
+ let result = await readReviewPage(next, paths, project.token, signal);
2565
+ if (!signal.aborted && context() === key) {
2566
+ snapshot = next;
2567
+ diffs = result.diffs;
2568
+ failures = result.failures;
2569
+ displayedKey = JSON.stringify([
2570
+ key,
2571
+ page,
2572
+ selected
2573
+ ]);
2574
+ }
2575
+ break;
2576
+ } catch (cause) {
2577
+ if (attempt === 0 && cause instanceof FilesRequestError && cause.status === 409) continue;
2578
+ throw cause;
2579
+ }
2580
+ }
2581
+ } catch (cause) {
2582
+ if (!signal.aborted) error = message$1(cause);
2583
+ } finally {
2584
+ if (request === controller) {
2585
+ loading = false;
2586
+ changed();
2587
+ }
2588
+ finishRead();
2589
+ }
2590
+ }
2591
+ async function loadHistory(older = false) {
2592
+ historyRequest?.abort();
2593
+ let path = handle.props.path;
2594
+ if (!path) {
2595
+ history = null;
2596
+ return;
2597
+ }
2598
+ let controller = new AbortController();
2599
+ historyRequest = controller;
2600
+ let signal = AbortSignal.any([handle.signal, controller.signal]);
2601
+ let query = new URLSearchParams({
2602
+ path,
2603
+ limit: "25"
2604
+ });
2605
+ if (older && history?.nextCursor && history.revision) {
2606
+ query.set("revision", history.revision);
2607
+ query.set("cursor", history.nextCursor);
2608
+ } else if (session.view.mode === "commits") query.set("revision", session.view.target);
2609
+ historyLoading = true;
2610
+ historyError = "";
2611
+ changed();
2612
+ try {
2613
+ let next = await requestFiles(`${routes.fileReviewApi.history.href()}?${query}`, project.token, signal, parseReviewHistory);
2614
+ if (next.path !== path) throw new FilesRequestError("The history response belongs to another path.", "response");
2615
+ if (!signal.aborted) history = next;
2616
+ } catch (cause) {
2617
+ if (!signal.aborted) historyError = message$1(cause);
2618
+ } finally {
2619
+ if (historyRequest === controller) {
2620
+ historyLoading = false;
2621
+ changed();
2622
+ }
2623
+ }
2624
+ }
2625
+ function selectPath(path) {
2626
+ if (session.view.multiple && snapshot) {
2627
+ let index = snapshot.entries.findIndex((entry) => entry.path === path);
2628
+ savePosition({
2629
+ ...defaultReviewPosition(),
2630
+ page: Math.max(0, Math.floor(index / 20))
2631
+ });
2632
+ lastKey = "";
2633
+ }
2634
+ handle.props.onSelect(path);
2635
+ changed();
2636
+ }
2637
+ function historyRevision(commit, action) {
2638
+ let endpoints = action === "parent" && commit.parents[0] ? {
2639
+ base: commit.parents[0],
2640
+ target: commit.commit
2641
+ } : action === "base" ? { base: commit.commit } : { target: commit.commit };
2642
+ historyOpen = false;
2643
+ changeView({
2644
+ mode: "commits",
2645
+ ...endpoints
2646
+ });
2647
+ if (commit.path !== handle.props.path) handle.props.onSelect(commit.path);
2648
+ }
2649
+ async function startEditor() {
2650
+ let path = handle.props.path;
2651
+ let mode = session.view.mode;
2652
+ if (!path || !editor) return;
2653
+ if (mode === "working") {
2654
+ if (!snapshot || snapshot.comparison.mode !== "working") await load();
2655
+ while (loading && reading) {
2656
+ await reading;
2657
+ if (handle.signal.aborted || handle.props.path !== path || session.view.mode !== mode) return;
2658
+ }
2659
+ if (handle.signal.aborted || handle.props.path !== path || session.view.mode !== mode) return;
2660
+ }
2661
+ let diff = diffs.find((diff) => diff.entry.path === path);
2662
+ let working = session.view.mode === "working" && diff?.newFile?.contents !== null && diff?.newFile?.revision;
2663
+ editor.begin(path, working ? "diff" : "file", working ? {
2664
+ workingRevision: diff.newFile.revision,
2665
+ file: diff.oldFile?.contents !== null && diff.oldFile ? {
2666
+ name: diff.oldFile.name,
2667
+ contents: diff.oldFile.contents
2668
+ } : null
2669
+ } : void 0);
2670
+ }
2671
+ function setPreferences(value) {
2672
+ preferences = value;
2673
+ writePreference("bud:editor-preferences:v1", JSON.stringify(value));
2674
+ changed();
2675
+ }
2676
+ function downloadDraft() {
2677
+ let draft = editor?.state?.draft;
2678
+ if (!draft) return;
2679
+ let url = URL.createObjectURL(new Blob([new TextEncoder().encode(draft.contents)], { type: "application/octet-stream" }));
2680
+ let link = window.document.createElement("a");
2681
+ link.href = url;
2682
+ link.download = draft.path.split("/").at(-1) ?? "draft.txt";
2683
+ link.click();
2684
+ setTimeout(() => URL.revokeObjectURL(url), 0);
2685
+ }
2686
+ async function resolveConflict(diff) {
2687
+ let identity = reviewConflictIdentity(diff.conflict);
2688
+ if (!identity || !editor) return;
2689
+ if (diff.entry.path !== handle.props.path) handle.props.onSelect(diff.entry.path);
2690
+ merge = {
2691
+ path: diff.entry.path,
2692
+ conflict: identity
2693
+ };
2694
+ await editor.begin(diff.entry.path);
2695
+ changed();
2696
+ }
2697
+ function selectDiscussion(thread, original = false) {
2698
+ if (!isCodeConversation(thread)) {
2699
+ window.location.assign(conversationHref(thread.id));
2700
+ return;
2701
+ }
2702
+ selectedThread = thread.id;
2703
+ handle.props.comments.loadThread(thread.id);
2704
+ openComments();
2705
+ draftAnchor = void 0;
2706
+ commentError = "";
2707
+ let located = commentLocations.find((entry) => entry.thread.id === thread.id);
2708
+ if (editor?.state?.draft.path === thread.anchor.path && !original) {
2709
+ changed();
2710
+ return;
2711
+ }
2712
+ let needsOldSide = thread.anchor.side === "old" && located?.source.side === void 0 && located?.resolution.state === "outdated";
2713
+ let usable = !original && !needsOldSide ? located : void 0;
2714
+ let path = usable?.source.viewPath ?? usable?.source.path ?? thread.anchor.path;
2715
+ if (thread.anchor.commit && (original || !usable)) {
2716
+ historical = {
2717
+ path: thread.anchor.path,
2718
+ commit: thread.anchor.commit
2719
+ };
2720
+ path = thread.anchor.path;
2721
+ session = {
2722
+ ...session,
2723
+ view: {
2724
+ ...session.view,
2725
+ mode: "source",
2726
+ multiple: false
2727
+ }
2728
+ };
2729
+ editor?.close();
2730
+ } else if (!usable) {
2731
+ historical = null;
2732
+ session = {
2733
+ ...session,
2734
+ view: {
2735
+ ...session.view,
2736
+ mode: thread.anchor.side === "old" ? "working" : "source",
2737
+ multiple: false
2738
+ }
2739
+ };
2740
+ }
2741
+ if (path !== handle.props.path) handle.props.onSelect(path);
2742
+ pendingFocus = thread.id;
2743
+ if (!usable || path !== handle.props.path) lastKey = "";
2744
+ changed();
2745
+ }
2746
+ function startComment(selection, fromEditor = false) {
2747
+ if (!handle.props.comments.ready || !handle.props.comments.snapshot.capabilities.create) return;
2748
+ openComments();
2749
+ appliedThread = requestedThread;
2750
+ selectedThread = void 0;
2751
+ commentError = "";
2752
+ try {
2753
+ let editing = editor?.state;
2754
+ if (fromEditor && editing && (draftDirty(editing.draft) || editing.conflict || editing.saving)) throw new Error("Save this draft before creating a code comment. Existing discussions remain available, and your draft is kept.");
2755
+ draftAnchor = captureCodeSelection(selection, fromEditor && editing && selection.side !== "deletions" ? [{
2756
+ path: editing.draft.path,
2757
+ revision: editing.draft.baseRevision,
2758
+ contents: editing.draft.contents,
2759
+ commit: null
2760
+ }] : commentSources, fromEditor ? void 0 : position().revision);
2761
+ } catch (cause) {
2762
+ draftAnchor = void 0;
2763
+ commentError = message$1(cause);
2764
+ }
2765
+ changed();
2766
+ }
2767
+ async function createComment(body) {
2768
+ if (!draftAnchor) throw new Error("Select code lines again before commenting.");
2769
+ if (editor?.state && draftDirty(editor.state.draft)) throw new Error("Save your draft before sending this code comment. The message is kept.");
2770
+ selectedThread = (await handle.props.comments.write({
2771
+ anchor: draftAnchor,
2772
+ body
2773
+ })).thread.id;
2774
+ draftAnchor = void 0;
2775
+ changed();
2776
+ }
2777
+ let mount = ref((node, signal) => {
2778
+ host = node;
2779
+ mounted = true;
2780
+ try {
2781
+ drafts = projectFileDrafts(project.id, preferenceDraftStorage);
2782
+ let saved = readPreference("bud:editor-preferences:v1");
2783
+ preferences = parseEditorPreferences(JSON.parse(typeof saved === "string" ? saved : "null"));
2784
+ } catch (cause) {
2785
+ error = message$1(cause);
2786
+ }
2787
+ if (drafts) {
2788
+ editor = createFileEditorController({
2789
+ signal,
2790
+ drafts,
2791
+ read: (path, signal) => requestFiles(`${routes.filesApi.read.href()}?${new URLSearchParams({ path })}`, project.token, signal, parseFileDocument),
2792
+ save: (body, signal) => requestFiles(routes.fileSavesApi.save.href(), project.token, signal, parseFileSaveResult, fetch, { body }),
2793
+ onChange: changed,
2794
+ onSaved: () => {
2795
+ lastKey = "";
2796
+ changed();
2797
+ }
2798
+ });
2799
+ handle.props.registerDraftGuard?.(editor.guard, signal);
2800
+ }
2801
+ actions = createFileReviewActionsController({
2802
+ signal,
2803
+ prepare: (body, signal) => requestFiles(routes.fileReviewActionsApi.prepare.href(), project.token, signal, parseReviewActionPlan, fetch, { body }),
2804
+ apply: (body, signal) => requestFiles(routes.fileReviewActionsApi.apply.href(), project.token, signal, parseReviewActionResult, fetch, { body }),
2805
+ recoveries: (signal) => requestFiles(routes.fileReviewActionsApi.recoveries.href(), project.token, signal, parseReviewActionRecoveries),
2806
+ guard(paths) {
2807
+ if (editor?.state?.saving) throw new Error("Wait for the current save to finish before reviewing changes.");
2808
+ if (paths.some((path) => {
2809
+ let draft = drafts?.get(path);
2810
+ return draft && draftDirty(draft);
2811
+ })) throw new Error("Save or explicitly discard the unsaved draft for this file before applying review actions. Your draft has been kept.");
2812
+ },
2813
+ async onApplied(result) {
2814
+ if (result.target === "worktree") editor?.close();
2815
+ if (merge && result.changedPaths.includes(merge.path) && result.target === "index") {
2816
+ merge = void 0;
2817
+ editor?.close();
2818
+ }
2819
+ lastKey = JSON.stringify([
2820
+ context(),
2821
+ position().page,
2822
+ handle.props.path
2823
+ ]);
2824
+ refresh = handle.props.refresh;
2825
+ let current = load();
2826
+ while (true) {
2827
+ let active = reading;
2828
+ await current;
2829
+ if (handle.signal.aborted || reading === active || !reading) break;
2830
+ current = reading;
2831
+ }
2832
+ if (error) throw new Error(`The write completed, but the review could not refresh: ${error}`);
2833
+ },
2834
+ onChange: changed
2835
+ });
2836
+ window.addEventListener("beforeunload", (event) => {
2837
+ drafts?.persist();
2838
+ if (drafts?.dirty.length) {
2839
+ event.preventDefault();
2840
+ event.returnValue = "";
2841
+ }
2842
+ }, { signal });
2843
+ try {
2844
+ session = parseReviewSession(readPreference(`bud:review:${project.id}:v1`), project.id);
2845
+ } catch {}
2846
+ readLocation(handle.props.href);
2847
+ renderedHref = handle.props.href;
2848
+ let readTheme = () => {
2849
+ theme = window.document.documentElement.dataset.theme === "dark" ? "dark" : "light";
2850
+ changed();
2851
+ };
2852
+ let observer = new MutationObserver(readTheme);
2853
+ observer.observe(window.document.documentElement, {
2854
+ attributes: true,
2855
+ attributeFilter: ["data-theme"]
2856
+ });
2857
+ readTheme();
2858
+ createCodeWorkerPool({
2859
+ signal,
2860
+ renderOptions: {
2861
+ theme: codeThemes,
2862
+ lineDiffType: session.view.highlights
2863
+ }
2864
+ }).then(async (value) => {
2865
+ await value.ready;
2866
+ if (!signal.aborted) {
2867
+ pool = value;
2868
+ poolSettled = true;
2869
+ changed();
2870
+ }
2871
+ }).catch(() => {
2872
+ poolSettled = true;
2873
+ changed();
2874
+ });
2875
+ signal.addEventListener("abort", () => {
2876
+ request?.abort();
2877
+ historyRequest?.abort();
2878
+ observer.disconnect();
2879
+ clearTimeout(persistTimer);
2880
+ pool?.dispose();
2881
+ drafts?.persist();
2882
+ writePreference(`bud:review:${project.id}:v1`, JSON.stringify(session));
2883
+ }, { once: true });
2884
+ });
2885
+ function readLocation(href) {
2886
+ let url = new URL(href);
2887
+ requestedThread = handle.props.conversation?.id ?? null;
2888
+ appliedThread = null;
2889
+ if (!requestedThread) {
2890
+ historical = null;
2891
+ source = null;
2892
+ }
2893
+ let parsed = readReviewLocation(url, project.id);
2894
+ if (readFileLocation(url, project.id).foreign) parsed = { view: {
2895
+ mode: "source",
2896
+ multiple: false
2897
+ } };
2898
+ if (parsed) {
2899
+ session = {
2900
+ ...session,
2901
+ view: {
2902
+ ...session.view,
2903
+ ...parsed.view
2904
+ }
2905
+ };
2906
+ if (parsed.position) session = rememberReviewPosition(session, reviewContextKey(session.view, parsed.position.selection?.path ?? handle.props.path), parsed.position);
2907
+ }
2908
+ lastKey = "";
2909
+ }
2910
+ return () => {
2911
+ if (mounted) {
2912
+ if (renderedHref !== handle.props.href || renderedConversation !== handle.props.conversation?.id) {
2913
+ renderedHref = handle.props.href;
2914
+ renderedConversation = handle.props.conversation?.id;
2915
+ if (pendingHref !== renderedHref) readLocation(renderedHref);
2916
+ pendingHref = void 0;
2917
+ }
2918
+ let target = handle.props.searchSelection;
2919
+ if (target && target.id !== searchSelectionId) {
2920
+ searchSelectionId = target.id;
2921
+ pendingSearch = target.match;
2922
+ searchNotice = "";
2923
+ handle.queueTask(() => {
2924
+ editor?.close();
2925
+ merge = void 0;
2926
+ changeView({
2927
+ mode: "source",
2928
+ multiple: false
2929
+ });
2930
+ lastKey = "";
2931
+ changed();
2932
+ });
2933
+ }
2934
+ if (observedPath !== handle.props.path) {
2935
+ observedPath = handle.props.path;
2936
+ merge = void 0;
2937
+ if (historical?.path !== handle.props.path) {
2938
+ historical = null;
2939
+ source = null;
2940
+ }
2941
+ handle.queueTask(() => editor?.closeOtherPath(handle.props.path));
2942
+ }
2943
+ let key = JSON.stringify([
2944
+ context(),
2945
+ position().page,
2946
+ handle.props.path
2947
+ ]);
2948
+ if (key !== lastKey || refresh !== handle.props.refresh) {
2949
+ lastKey = key;
2950
+ refresh = handle.props.refresh;
2951
+ handle.queueTask(() => {
2952
+ load();
2953
+ });
2954
+ }
2955
+ let nextHistoryKey = JSON.stringify([handle.props.path, session.view.mode === "commits" ? session.view.target : handle.props.repository?.head]);
2956
+ if (historyOpen && nextHistoryKey !== historyKey) {
2957
+ historyKey = nextHistoryKey;
2958
+ handle.queueTask(() => {
2959
+ loadHistory();
2960
+ });
2961
+ }
2962
+ if (pool && appliedHighlights !== session.view.highlights) {
2963
+ appliedHighlights = session.view.highlights;
2964
+ let owned = pool;
2965
+ handle.queueTask(() => {
2966
+ owned.pool.setRenderOptions({ lineDiffType: session.view.highlights }).catch((cause) => {
2967
+ if (!handle.signal.aborted) {
2968
+ error = `Could not update highlighting. ${message$1(cause)}`;
2969
+ changed();
2970
+ }
2971
+ });
2972
+ });
2973
+ }
2974
+ }
2975
+ let editing = editor?.state;
2976
+ let comments = handle.props.comments;
2977
+ if (selectedThread && comments.ready) {
2978
+ if (!comments.snapshot.threads.find((thread) => thread.id === selectedThread)?.messagesLoaded && !comments.loadingThread(selectedThread)) {
2979
+ let id = selectedThread;
2980
+ handle.queueTask(() => {
2981
+ comments.loadThread(id);
2982
+ });
2983
+ }
2984
+ }
2985
+ let nextSourceSignature = JSON.stringify([
2986
+ document?.entry.path,
2987
+ document?.revision,
2988
+ source?.path,
2989
+ source?.commit,
2990
+ source?.file?.revision,
2991
+ diffs.map((diff) => diff.revision),
2992
+ editing?.key,
2993
+ editing?.draft.baseRevision
2994
+ ]);
2995
+ if (nextSourceSignature !== sourceSignature || locationsVersion !== comments.version) {
2996
+ sourceSignature = nextSourceSignature;
2997
+ locationsVersion = comments.version;
2998
+ commentSources = reviewCommentSources(document, diffs, source);
2999
+ commentLocations = locateCodeConversations(comments.snapshot.threads, commentSources);
3000
+ }
3001
+ if (locatedEditor !== editing?.key) {
3002
+ locatedEditor = editing?.key;
3003
+ editorLocations = void 0;
3004
+ }
3005
+ if (editing && editorLocations) commentLocations = [...editorLocations];
3006
+ if (requestedThread && requestedThread !== appliedThread && comments.loaded && !loading && (document || source || snapshot || error)) {
3007
+ let thread = comments.snapshot.threads.find((thread) => thread.id === requestedThread);
3008
+ if (thread) {
3009
+ appliedThread = requestedThread;
3010
+ handle.queueTask(() => selectDiscussion(thread, handle.props.conversation?.original ?? false));
3011
+ } else {
3012
+ panelOpen = true;
3013
+ let id = requestedThread;
3014
+ handle.queueTask(() => {
3015
+ comments.loadThread(id);
3016
+ });
3017
+ }
3018
+ }
3019
+ if (pendingFocus && !loading) {
3020
+ let location = commentLocations.find((entry) => entry.thread.id === pendingFocus);
3021
+ if (location) {
3022
+ pendingFocus = null;
3023
+ let change = codeConversationPosition(location);
3024
+ if (change) handle.queueTask(() => savePosition(change, true));
3025
+ }
3026
+ }
3027
+ let commentsShown = panelOpen && comments.ready;
3028
+ if (commentsShown !== reportedComments) {
3029
+ reportedComments = commentsShown;
3030
+ handle.queueTask(() => handle.props.onComments?.(commentsShown));
3031
+ }
3032
+ let dirtyPaths = drafts?.dirty.map((draft) => draft.path) ?? [];
3033
+ if (dirtyPaths.join("\0") !== reportedDrafts) {
3034
+ reportedDrafts = dirtyPaths.join("\0");
3035
+ handle.queueTask(() => handle.props.onDrafts?.(dirtyPaths));
3036
+ }
3037
+ let codeComments = {
3038
+ threads: comments.snapshot.threads,
3039
+ locations: commentLocations,
3040
+ version: comments.version,
3041
+ selected: selectedThread,
3042
+ onShow: () => {
3043
+ openComments();
3044
+ changed();
3045
+ },
3046
+ onOpen: (id) => {
3047
+ let thread = comments.snapshot.threads.find((thread) => thread.id === id);
3048
+ if (thread) selectDiscussion(thread);
3049
+ },
3050
+ onCreate: comments.snapshot.capabilities.create ? (selection) => startComment(selection) : void 0,
3051
+ onLocations: (locations) => {
3052
+ if (editor?.state?.key !== editing?.key) return;
3053
+ editorLocations = locations;
3054
+ changed();
3055
+ }
3056
+ };
3057
+ return /* @__PURE__ */ jsxs("div", {
3058
+ class: "flex h-full min-h-0 flex-col",
3059
+ mix: mount,
3060
+ children: [
3061
+ (comments.loaded ? !comments.ready : project.storage.threads?.state !== "ready") ? /* @__PURE__ */ jsx(Text, {
3062
+ as: "p",
3063
+ variant: "ui-caption",
3064
+ tone: "muted",
3065
+ role: "status",
3066
+ class: "shrink-0 border-b border-line px-3 py-1.5",
3067
+ children: commentsUnavailable
3068
+ }) : null,
3069
+ searchNotice ? /* @__PURE__ */ jsx(Text, {
3070
+ as: "p",
3071
+ variant: "ui-caption",
3072
+ role: "status",
3073
+ class: "shrink-0 border-b border-line px-3 py-1.5",
3074
+ children: searchNotice
3075
+ }) : null,
3076
+ editor?.openError ? /* @__PURE__ */ jsx(Text, {
3077
+ as: "p",
3078
+ variant: "ui-caption",
3079
+ role: "alert",
3080
+ class: "shrink-0 border-b border-line px-3 py-1.5 text-red-600 dark:text-red-400",
3081
+ children: editor.openError
3082
+ }) : null,
3083
+ editor?.opening ? /* @__PURE__ */ jsx(Text, {
3084
+ as: "p",
3085
+ variant: "ui-caption",
3086
+ role: "status",
3087
+ class: "sr-only",
3088
+ children: "Reading current file for editing…"
3089
+ }) : null,
3090
+ editing && editor && merge?.path === editing.draft.path ? /* @__PURE__ */ jsx(FileConflictPanel, {
3091
+ chrome: handle.props.chrome,
3092
+ file: {
3093
+ name: editing.draft.path,
3094
+ contents: editing.draft.contents,
3095
+ cacheKey: editing.draft.baseRevision
3096
+ },
3097
+ dirty: draftDirty(editing.draft),
3098
+ saving: editing.saving,
3099
+ stale: editing.conflict,
3100
+ error: editing.error,
3101
+ status: editing.status,
3102
+ theme,
3103
+ workerPool: pool?.pool,
3104
+ onChange: editor.change,
3105
+ onSave: () => {
3106
+ editor?.save();
3107
+ },
3108
+ onMarkResolved: () => {
3109
+ if (merge) actions?.prepare({
3110
+ kind: "mark-resolved",
3111
+ path: merge.path,
3112
+ expectedRevision: editing.draft.baseRevision,
3113
+ conflict: merge.conflict
3114
+ });
3115
+ },
3116
+ onEdit: () => {
3117
+ merge = void 0;
3118
+ editor?.begin(editing.draft.path);
3119
+ changed();
3120
+ },
3121
+ onClose: () => {
3122
+ merge = void 0;
3123
+ editor?.close();
3124
+ lastKey = "";
3125
+ changed();
3126
+ }
3127
+ }, `merge:${editing.key}`) : editing && editor ? /* @__PURE__ */ jsx(FileEditorPanel, {
3128
+ chrome: handle.props.chrome,
3129
+ file: editing.file,
3130
+ oldFile: editing.oldFile,
3131
+ initialMode: editing.mode,
3132
+ sessionKey: editing.key,
3133
+ comments: comments.ready ? {
3134
+ ...codeComments,
3135
+ onCreate: comments.snapshot.capabilities.create ? (selection) => startComment(selection, true) : void 0
3136
+ } : void 0,
3137
+ commentRevision: editing.draft.baseRevision,
3138
+ baselineLabel: editing.baselineLabel,
3139
+ dirty: draftDirty(editing.draft),
3140
+ saving: editing.saving,
3141
+ conflict: editing.conflict,
3142
+ reviewingConflict: Boolean(editing.latest),
3143
+ status: editing.status,
3144
+ error: editing.error,
3145
+ storageWarning: drafts?.warning ?? "",
3146
+ preferences,
3147
+ theme,
3148
+ workerPool: pool?.pool,
3149
+ onChange: editor.change,
3150
+ onSave: () => {
3151
+ editor?.save();
3152
+ },
3153
+ onClose: () => {
3154
+ editor?.close();
3155
+ lastKey = "";
3156
+ changed();
3157
+ },
3158
+ onDiscard: () => {
3159
+ editor?.discard();
3160
+ },
3161
+ onDownload: downloadDraft,
3162
+ onCheckLatest: () => {
3163
+ editor?.checkLatest();
3164
+ },
3165
+ onAcceptMerge: editor.acceptMerge,
3166
+ onPreferences: setPreferences
3167
+ }, editing.key) : /* @__PURE__ */ jsx(FileReviewPanel, {
3168
+ chrome: handle.props.chrome,
3169
+ path: handle.props.path,
3170
+ repository: handle.props.repository,
3171
+ contextKey: context(),
3172
+ view: session.view,
3173
+ position: position(),
3174
+ document: poolSettled ? document : null,
3175
+ source: poolSettled ? source : null,
3176
+ comments: comments.ready ? codeComments : void 0,
3177
+ snapshot,
3178
+ diffs: poolSettled ? diffs : [],
3179
+ failures,
3180
+ loading: loading || !poolSettled,
3181
+ error,
3182
+ history,
3183
+ historyOpen,
3184
+ historyLoading,
3185
+ historyError,
3186
+ theme,
3187
+ workerPool: pool?.pool,
3188
+ onEdit: project.files.edit && editor && handle.props.path && !historical ? startEditor : void 0,
3189
+ onReviewAction: actions ? (intent) => {
3190
+ actions?.prepare(intent);
3191
+ } : void 0,
3192
+ onReviewUndo: actions ? () => {
3193
+ actions?.recoveries();
3194
+ } : void 0,
3195
+ onResolve: (diff) => {
3196
+ resolveConflict(diff);
3197
+ },
3198
+ editLabel: handle.props.path && drafts?.get(handle.props.path) && draftDirty(drafts.get(handle.props.path)) ? "Resume draft" : "Edit file",
3199
+ onChange: changeView,
3200
+ onPosition: savePosition,
3201
+ onSelect: selectPath,
3202
+ onPage: (page) => {
3203
+ savePosition({
3204
+ ...defaultReviewPosition(),
3205
+ page
3206
+ }, true);
3207
+ },
3208
+ onRetry: () => {
3209
+ load();
3210
+ },
3211
+ onHistory: () => {
3212
+ historyOpen = !historyOpen;
3213
+ if (historyOpen) historyKey = "";
3214
+ else historyRequest?.abort();
3215
+ changed();
3216
+ },
3217
+ onHistoryPage: (older) => {
3218
+ loadHistory(older);
3219
+ },
3220
+ onHistoryRevision: historyRevision,
3221
+ onCopyLink: async (position) => {
3222
+ let path = position.selection?.path ?? handle.props.path;
3223
+ let view = snapshot?.comparison.mode === "commits" ? {
3224
+ ...session.view,
3225
+ ...snapshot.comparison
3226
+ } : session.view;
3227
+ let href = reviewHref(path, view, position);
3228
+ await navigator.clipboard.writeText(new URL(href, window.location.origin).href);
3229
+ }
3230
+ }),
3231
+ panelOpen && comments.ready ? /* @__PURE__ */ jsx(ConversationSidebar, {
3232
+ threads: comments.snapshot.threads.filter(isCodeConversation),
3233
+ renderThreadLinks: (thread) => /* @__PURE__ */ jsx(ProjectThreadLinks, {
3234
+ part: "links",
3235
+ project,
3236
+ thread,
3237
+ canLink: comments.snapshot.capabilities.link,
3238
+ busy: comments.busy,
3239
+ onChange: async () => {
3240
+ await comments.load();
3241
+ await comments.loadThread(thread.id, true);
3242
+ }
3243
+ }),
3244
+ renderThreadActions: (thread) => /* @__PURE__ */ jsx(ProjectThreadLinks, {
3245
+ part: "action",
3246
+ project,
3247
+ thread,
3248
+ canLink: comments.snapshot.capabilities.link,
3249
+ busy: comments.busy,
3250
+ onChange: async () => {
3251
+ await comments.load();
3252
+ await comments.loadThread(thread.id, true);
3253
+ }
3254
+ }),
3255
+ capabilities: comments.snapshot.capabilities,
3256
+ selected: selectedThread,
3257
+ currentPath: handle.props.path,
3258
+ locations: commentLocations,
3259
+ draft: draftAnchor,
3260
+ loaded: comments.loaded,
3261
+ busy: comments.busy,
3262
+ now: comments.now,
3263
+ error: commentError || comments.error,
3264
+ onSelect: (thread) => selectDiscussion(thread),
3265
+ onCloseThread: () => {
3266
+ selectedThread = void 0;
3267
+ changed();
3268
+ },
3269
+ onClose: closeComments,
3270
+ onWrite: comments.write,
3271
+ onCreate: createComment,
3272
+ onCancelDraft: () => {
3273
+ draftAnchor = void 0;
3274
+ changed();
3275
+ },
3276
+ onReload: () => {
3277
+ comments.load();
3278
+ },
3279
+ onSync: comments.snapshot.capabilities.sync ? () => {
3280
+ comments.sync().catch(() => {});
3281
+ } : void 0,
3282
+ onOriginal: (thread) => selectDiscussion(thread, true),
3283
+ onCopyLink: (thread) => navigator.clipboard.writeText(new URL(conversationHref(thread.id), window.location.origin).href)
3284
+ }) : null,
3285
+ actions?.state ? /* @__PURE__ */ jsx(ReviewActionDialog, {
3286
+ state: actions.state,
3287
+ theme,
3288
+ onApply: () => {
3289
+ actions?.apply();
3290
+ },
3291
+ onClose: actions.close,
3292
+ onUndo: (recoveryId) => {
3293
+ actions?.prepare({
3294
+ kind: "undo",
3295
+ recoveryId
3296
+ });
3297
+ },
3298
+ onRecoveries: () => {
3299
+ actions?.recoveries();
3300
+ }
3301
+ }) : null
3302
+ ]
3303
+ });
3304
+ };
3305
+ }
3306
+ //#endregion
3307
+ //#region app/client/project-files.tsx
3308
+ function message(error) {
3309
+ return error instanceof Error ? error.message : "Could not load project files.";
3310
+ }
3311
+ function requestProjectFiles(token, view, signal) {
3312
+ let params = new URLSearchParams({
3313
+ includeIgnored: String(view.includeIgnored),
3314
+ includeDotfiles: "true"
3315
+ });
3316
+ return Promise.all([requestFiles(`${routes.filesApi.index.href()}?${params}`, token, signal, parseFileIndex), requestFiles(routes.fileReviewApi.repository.href(), token, signal, parseReviewRepository)]);
3317
+ }
3318
+ /**
3319
+ * Reads the stored view, the file the URL asks for and the first listing for the screen's load.
3320
+ * A listing this browser stored for the same view shows at once.
3321
+ * @param project - The connected project.
3322
+ * @param signal - The screen's lifetime.
3323
+ * @param url - The screen's initial typed location.
3324
+ * @returns The listing, or the error the workspace shows until a refresh succeeds.
3325
+ */
3326
+ async function loadFiles(project, signal, url) {
3327
+ let view;
3328
+ try {
3329
+ view = parseFileView(readPreference(`bud:files:${project.id}:v1`), project.id);
3330
+ } catch {
3331
+ view = defaultFileView(project.id);
3332
+ }
3333
+ let location = readFileLocation(url, project.id);
3334
+ if (location.explicit) view = rememberFile(view, location.path);
3335
+ let store = await projectStore(project);
3336
+ let stored = storedFiles(store);
3337
+ if (stored && stored.includeIgnored === view.includeIgnored) {
3338
+ let { index, repository } = stored;
3339
+ view = reconcileFileView(view, null, index, repository);
3340
+ return {
3341
+ view,
3342
+ index,
3343
+ repository,
3344
+ error: "",
3345
+ store,
3346
+ stored: true
3347
+ };
3348
+ }
3349
+ try {
3350
+ let [index, repository] = await requestProjectFiles(project.token, view, signal);
3351
+ await saveFiles(store, index, repository, view.includeIgnored);
3352
+ return {
3353
+ view: reconcileFileView(view, null, index, repository),
3354
+ index,
3355
+ repository,
3356
+ error: "",
3357
+ store,
3358
+ stored: false
3359
+ };
3360
+ } catch (cause) {
3361
+ signal.throwIfAborted();
3362
+ return {
3363
+ view,
3364
+ index: null,
3365
+ repository: null,
3366
+ error: message(cause),
3367
+ store,
3368
+ stored: false
3369
+ };
3370
+ }
3371
+ }
3372
+ /**
3373
+ * The server capabilities and browser persistence stay outside the shared navigator.
3374
+ * @param handle - The project, its first listing and the component's owned lifecycle.
3375
+ * @returns The component render function.
3376
+ */
3377
+ function ProjectFiles(handle) {
3378
+ let project = handle.props.project;
3379
+ let { view, index, repository, error, store } = handle.props.initial;
3380
+ let listed = () => {
3381
+ let next = storedFiles(store);
3382
+ if (!next || next.includeIgnored !== view.includeIgnored) return;
3383
+ let previous = view.selected;
3384
+ let retained = view.selected !== null && drafted(view.selected);
3385
+ view = reconcileFileView(view, retained ? null : index, next.index, next.repository);
3386
+ index = next.index;
3387
+ repository = next.repository;
3388
+ if (previous !== view.selected) location(true);
3389
+ changed();
3390
+ };
3391
+ function drafted(path) {
3392
+ try {
3393
+ return projectFileDrafts(project.id, preferenceDraftStorage).dirty.some((draft) => draft.path === path);
3394
+ } catch {
3395
+ return false;
3396
+ }
3397
+ }
3398
+ let subscriptions = [
3399
+ store.listings.subscribeChanges(listed),
3400
+ store.files.subscribeChanges(listed),
3401
+ store.repositories.subscribeChanges(listed)
3402
+ ];
3403
+ let loading = false;
3404
+ let renderedHref = handle.props.href;
3405
+ let pendingHref;
3406
+ let deferredLocation = false;
3407
+ let notice = "";
3408
+ let listRequest;
3409
+ let storageTimer;
3410
+ let refreshTimer;
3411
+ let mounted = false;
3412
+ let refreshing = false;
3413
+ let trailingRefresh = false;
3414
+ let settling = false;
3415
+ let pendingContentRefresh = false;
3416
+ let contentRefresh = 0;
3417
+ let searchOpen = false, searchSerial = 0;
3418
+ let searchSelection;
3419
+ let dirty = [];
3420
+ let commentsOpen = false;
3421
+ let search = createFileSearchController({
3422
+ signal: handle.signal,
3423
+ onChange: changed,
3424
+ request: (query, signal) => requestFiles(`${routes.fileSearchApi.search.href()}?${fileSearchParameters(query)}`, project.token, signal, parseFileSearchResult)
3425
+ });
3426
+ function closeSearch() {
3427
+ search.cancel("");
3428
+ searchOpen = false;
3429
+ changed();
3430
+ }
3431
+ let guards = createFileMutationGuards();
3432
+ let comments = createProjectComments({
3433
+ store: projectStore(project),
3434
+ token: project.token,
3435
+ signal: handle.signal,
3436
+ onChange: changed
3437
+ });
3438
+ let countedVersion;
3439
+ let commentCounts = {};
3440
+ let mutations = createFileMutationController({
3441
+ signal: handle.signal,
3442
+ index: () => index,
3443
+ guards,
3444
+ onChange() {
3445
+ if (mutations.state?.phase === "applying") {
3446
+ listRequest?.abort();
3447
+ settling = true;
3448
+ }
3449
+ changed();
3450
+ },
3451
+ transport: {
3452
+ prepare: (intent, signal) => requestFiles(routes.fileMutationsApi.prepare.href(), project.token, signal, parseMutationPlan, fetch, { body: intent }),
3453
+ apply: (plan, signal) => requestFiles(routes.fileMutationsApi.apply.href(), project.token, signal, parseMutationResult, fetch, { body: {
3454
+ id: plan.id,
3455
+ revision: plan.revision
3456
+ } }),
3457
+ recoveries: (signal) => requestFiles(routes.fileMutationsApi.recoveries.href(), project.token, signal, parseRecoveries)
3458
+ },
3459
+ async onApplied(intent, result) {
3460
+ view = mutationFileView(view, intent.kind, result);
3461
+ let count = result.items.filter((item) => item.status === "applied").length;
3462
+ if (count) toast.success(`${count} file action${count === 1 ? "" : "s"} completed`, { description: result.recoveryId ? "Original contents are available in Recovery." : void 0 });
3463
+ else toast.error("The file action could not be applied", { description: "Your project was refreshed." });
3464
+ location(true);
3465
+ persist();
3466
+ pendingContentRefresh = true;
3467
+ await load(true);
3468
+ settling = false;
3469
+ },
3470
+ async onUncertain() {
3471
+ pendingContentRefresh = true;
3472
+ await load(true);
3473
+ settling = false;
3474
+ }
3475
+ });
3476
+ function changed() {
3477
+ if (!handle.signal.aborted) handle.update();
3478
+ }
3479
+ function persist() {
3480
+ clearTimeout(storageTimer);
3481
+ storageTimer = setTimeout(() => writePreference(`bud:files:${project.id}:v1`, JSON.stringify(view)), 200);
3482
+ }
3483
+ function location(replace) {
3484
+ let root = fileHref(null);
3485
+ let conversation = readConversationLocation(window.location.href) === handle.props.conversation?.id;
3486
+ if (!handle.props.active || !conversation && window.location.pathname !== root && !window.location.pathname.startsWith(`${root}/`)) {
3487
+ deferredLocation = true;
3488
+ return;
3489
+ }
3490
+ deferredLocation = false;
3491
+ if (conversation && replace) return;
3492
+ let review = readReviewLocation(conversation ? handle.props.href : window.location.href, project.id);
3493
+ let href = review?.view.mode && review.view.mode !== "source" ? reviewHref(view.selected, {
3494
+ ...defaultReviewView(),
3495
+ ...review.view
3496
+ }) : fileHref(view.selected);
3497
+ if (window.location.pathname + window.location.search + window.location.hash === href) return;
3498
+ pendingHref = new URL(href, window.location.origin).href;
3499
+ window.history[replace ? "replaceState" : "pushState"]({
3500
+ ...window.history.state,
3501
+ budFile: view.selected
3502
+ }, "", href);
3503
+ }
3504
+ function updateView(change) {
3505
+ let next = {
3506
+ ...view,
3507
+ ...change
3508
+ };
3509
+ let includeChanged = next.includeIgnored !== view.includeIgnored;
3510
+ view = next;
3511
+ persist();
3512
+ changed();
3513
+ if (includeChanged) scheduleRefresh();
3514
+ }
3515
+ function select(path, selection) {
3516
+ let previous = view.selected;
3517
+ view = rememberFile(view, path, selection);
3518
+ notice = "";
3519
+ persist();
3520
+ if (previous !== path) location(false);
3521
+ changed();
3522
+ }
3523
+ function closeTab(path) {
3524
+ if (dirty.includes(path)) {
3525
+ select(path, [path]);
3526
+ toast("Save or discard this draft to close its tab");
3527
+ changed();
3528
+ return;
3529
+ }
3530
+ let previous = view.selected;
3531
+ view = closeFileTab(view, path);
3532
+ persist();
3533
+ if (previous !== view.selected) location(false);
3534
+ changed();
3535
+ }
3536
+ async function load(afterMutation = false) {
3537
+ if (!afterMutation && mutations.state?.phase === "applying") {
3538
+ trailingRefresh = true;
3539
+ return;
3540
+ }
3541
+ if (refreshing) {
3542
+ trailingRefresh = true;
3543
+ return;
3544
+ }
3545
+ refreshing = true;
3546
+ loading = true;
3547
+ error = "";
3548
+ let controller = new AbortController();
3549
+ listRequest = controller;
3550
+ let signal = AbortSignal.any([handle.signal, controller.signal]);
3551
+ changed();
3552
+ try {
3553
+ let [nextIndex, nextRepository] = await requestProjectFiles(project.token, view, signal);
3554
+ if (signal.aborted) return;
3555
+ let previous = view.selected;
3556
+ view = reconcileFileView(view, index, nextIndex, nextRepository);
3557
+ index = nextIndex;
3558
+ repository = nextRepository;
3559
+ await saveFiles(store, nextIndex, nextRepository, view.includeIgnored);
3560
+ persist();
3561
+ if (previous !== view.selected) location(true);
3562
+ if (pendingContentRefresh) contentRefresh++;
3563
+ pendingContentRefresh = false;
3564
+ } catch (cause) {
3565
+ if (!signal.aborted) error = message(cause);
3566
+ } finally {
3567
+ controller.abort();
3568
+ refreshing = false;
3569
+ loading = false;
3570
+ changed();
3571
+ if (trailingRefresh && !handle.signal.aborted) {
3572
+ trailingRefresh = false;
3573
+ scheduleRefresh();
3574
+ }
3575
+ }
3576
+ }
3577
+ function scheduleRefresh() {
3578
+ clearTimeout(refreshTimer);
3579
+ refreshTimer = setTimeout(() => {
3580
+ load();
3581
+ }, 100);
3582
+ }
3583
+ async function refresh() {
3584
+ if (loading) return;
3585
+ let controller = new AbortController();
3586
+ try {
3587
+ await requestFiles(routes.filesApi.refresh.href(), project.token, AbortSignal.any([handle.signal, controller.signal]), parseFileChange);
3588
+ pendingContentRefresh = true;
3589
+ await load();
3590
+ } catch (cause) {
3591
+ if (!handle.signal.aborted) {
3592
+ error = message(cause);
3593
+ changed();
3594
+ }
3595
+ } finally {
3596
+ controller.abort();
3597
+ }
3598
+ }
3599
+ let mount = ref((_node, signal) => {
3600
+ if (mounted) return;
3601
+ mounted = true;
3602
+ let opened = readFileLocation(handle.props.href, project.id);
3603
+ foreignNotice(opened.foreign);
3604
+ if (!opened.foreign && (opened.explicit ? opened.path !== view.selected : !view.selected)) location(true);
3605
+ persist();
3606
+ let stopFiles = projectLive(project.token).subscribe("files", {
3607
+ onEvent(data) {
3608
+ let update = parseFileUpdate(data);
3609
+ pendingContentRefresh = true;
3610
+ if (settling) {
3611
+ trailingRefresh = true;
3612
+ return;
3613
+ }
3614
+ if (update.change.reset || refreshing || view.includeIgnored) return scheduleRefresh();
3615
+ applyFileUpdate(store, update).then(() => {
3616
+ contentRefresh++;
3617
+ pendingContentRefresh = false;
3618
+ changed();
3619
+ });
3620
+ },
3621
+ onResync() {
3622
+ pendingContentRefresh = true;
3623
+ scheduleRefresh();
3624
+ }
3625
+ });
3626
+ if (handle.props.initial.stored) scheduleRefresh();
3627
+ comments.load();
3628
+ signal.addEventListener("abort", () => {
3629
+ stopFiles();
3630
+ for (let subscription of subscriptions) subscription.unsubscribe();
3631
+ listRequest?.abort();
3632
+ clearTimeout(storageTimer);
3633
+ clearTimeout(refreshTimer);
3634
+ writePreference(`bud:files:${project.id}:v1`, JSON.stringify(view));
3635
+ }, { once: true });
3636
+ });
3637
+ function foreignNotice(foreign) {
3638
+ notice = foreign ? "This file link belongs to another project. Choose a file from this project to continue." : "";
3639
+ }
3640
+ return () => {
3641
+ if (renderedHref !== handle.props.href) {
3642
+ renderedHref = handle.props.href;
3643
+ if (pendingHref !== renderedHref) {
3644
+ deferredLocation = false;
3645
+ let selected = readFileLocation(renderedHref, project.id);
3646
+ if (selected.explicit) view = rememberFile(view, selected.path);
3647
+ foreignNotice(selected.foreign);
3648
+ }
3649
+ pendingHref = void 0;
3650
+ }
3651
+ if (deferredLocation && handle.props.active) handle.queueTask(() => location(true));
3652
+ if (countedVersion !== comments.version) {
3653
+ countedVersion = comments.version;
3654
+ commentCounts = codeCommentCounts(comments.snapshot.threads);
3655
+ }
3656
+ return /* @__PURE__ */ jsxs("div", {
3657
+ class: `relative h-full min-h-0 ${commentsOpen ? "md:pe-80" : ""}`,
3658
+ mix: mount,
3659
+ children: [
3660
+ /* @__PURE__ */ jsx(FileWorkspace, {
3661
+ projectName: project.name,
3662
+ view,
3663
+ index,
3664
+ repository,
3665
+ loading,
3666
+ error,
3667
+ notice,
3668
+ commentCounts,
3669
+ dirty,
3670
+ onViewChange: updateView,
3671
+ onSelect: select,
3672
+ onCloseTab: closeTab,
3673
+ onSearch: () => {
3674
+ searchOpen = true;
3675
+ changed();
3676
+ },
3677
+ onRefresh: () => {
3678
+ refresh();
3679
+ },
3680
+ mutations: project.files.edit ? {
3681
+ busy: mutations.busy,
3682
+ open: (request) => mutations.open(request),
3683
+ move: (entries, source) => mutations.open({
3684
+ action: source === "rename" ? "rename" : "move",
3685
+ paths: entries.map((entry) => entry.path),
3686
+ entries
3687
+ }, true),
3688
+ recoveries: () => mutations.recoveries()
3689
+ } : void 0,
3690
+ sidebar: searchOpen ? /* @__PURE__ */ jsx(FileSearchPanel, {
3691
+ ...search.state,
3692
+ onSearch: (query) => {
3693
+ search.search(query);
3694
+ },
3695
+ onCancel: () => search.cancel(),
3696
+ onClose: closeSearch,
3697
+ onSelect: (match) => {
3698
+ searchSelection = {
3699
+ id: ++searchSerial,
3700
+ match
3701
+ };
3702
+ select(match.path, [match.path]);
3703
+ if (!window.matchMedia("(min-width: 768px)").matches) closeSearch();
3704
+ }
3705
+ }) : void 0,
3706
+ content: (chrome) => /* @__PURE__ */ jsx(ProjectFileReview, {
3707
+ project,
3708
+ href: handle.props.href,
3709
+ path: view.selected,
3710
+ repository,
3711
+ refresh: contentRefresh,
3712
+ store,
3713
+ chrome,
3714
+ onSelect: (path) => select(path, [path]),
3715
+ onComments: (open) => {
3716
+ commentsOpen = open;
3717
+ changed();
3718
+ },
3719
+ onDrafts: (paths) => {
3720
+ if (paths.join("\0") === dirty.join("\0")) return;
3721
+ dirty = paths;
3722
+ changed();
3723
+ },
3724
+ registerDraftGuard: guards.register,
3725
+ comments,
3726
+ conversation: handle.props.conversation,
3727
+ searchSelection
3728
+ })
3729
+ }),
3730
+ mutations.state ? /* @__PURE__ */ jsx(FileMutationDialog, {
3731
+ state: mutations.state,
3732
+ onDestination: mutations.destination,
3733
+ onSubmit: () => {
3734
+ mutations.submit();
3735
+ },
3736
+ onClose: mutations.close,
3737
+ onRecovery: mutations.recoveries
3738
+ }) : null,
3739
+ mutations.recovery.open ? /* @__PURE__ */ jsx(FileRecoveryDialog, {
3740
+ recoveries: mutations.recovery.recoveries,
3741
+ loading: mutations.recovery.loading,
3742
+ error: mutations.recovery.error,
3743
+ onClose: mutations.closeRecoveries,
3744
+ onRefresh: () => {
3745
+ mutations.refreshRecoveries();
3746
+ },
3747
+ onRestore: (recoveryId, paths) => mutations.open({
3748
+ action: "restore",
3749
+ recoveryId,
3750
+ paths
3751
+ })
3752
+ }) : null
3753
+ ]
3754
+ });
3755
+ };
3756
+ }
3757
+ //#endregion
3758
+ //#region app/screens/files.tsx
3759
+ var files_default = defineScreen({
3760
+ routes: fileRoutes,
3761
+ load: ({ project, signal, url }) => loadFiles(project, signal, url),
3762
+ render: (files, { project, url, active, conversation }) => /* @__PURE__ */ jsx(ProjectFiles, {
3763
+ project,
3764
+ initial: files,
3765
+ href: url.href,
3766
+ active,
3767
+ conversation
3768
+ })
3769
+ });
3770
+ //#endregion
3771
+ export { files_default as default };