@sanurb/ringi 0.2.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 (423) hide show
  1. package/dist/cli.mjs +2132 -0
  2. package/dist/cli.mjs.map +1 -0
  3. package/dist/mcp.mjs +1057 -0
  4. package/dist/mcp.mjs.map +1 -0
  5. package/dist/runtime.mjs +3116 -0
  6. package/dist/runtime.mjs.map +1 -0
  7. package/package.json +15 -14
  8. package/server/nitro.json +17 -0
  9. package/server/public/assets/ClientOnly-QdfAxyFs.js +1 -0
  10. package/server/public/assets/_reviewId-CmXHvWLn.js +1 -0
  11. package/server/public/assets/_reviewId-DdOpDx4U.js +1 -0
  12. package/server/public/assets/abap-B1dkBSPn.js +1 -0
  13. package/server/public/assets/action-bar-DLRNvLjj.js +45 -0
  14. package/server/public/assets/actionscript-3-BT4ibYlP.js +1 -0
  15. package/server/public/assets/ada-CD92zeps.js +1 -0
  16. package/server/public/assets/andromeeda-DqSmgxi0.js +1 -0
  17. package/server/public/assets/angular-html-BDC0PfKr.js +1 -0
  18. package/server/public/assets/angular-ts-B9yoQMtj.js +1 -0
  19. package/server/public/assets/apache-D5suuoa_.js +1 -0
  20. package/server/public/assets/apex-BL-m4VHy.js +1 -0
  21. package/server/public/assets/apl-CldhY0Pn.js +1 -0
  22. package/server/public/assets/applescript-CLiBqvKT.js +1 -0
  23. package/server/public/assets/ara-LdDF8cmv.js +1 -0
  24. package/server/public/assets/asciidoc-2DZ9hC2N.js +1 -0
  25. package/server/public/assets/asm-0ZPGRSUy.js +1 -0
  26. package/server/public/assets/astro-DR6labZJ.js +1 -0
  27. package/server/public/assets/aurora-x-Da7Zfvbg.js +1 -0
  28. package/server/public/assets/awk-Bn0gn_B_.js +1 -0
  29. package/server/public/assets/ayu-dark-bqYKoqpM.js +1 -0
  30. package/server/public/assets/ayu-light-C45jTIzZ.js +1 -0
  31. package/server/public/assets/ayu-mirage-BV_FCTQi.js +1 -0
  32. package/server/public/assets/ballerina-D2nw_w8Q.js +1 -0
  33. package/server/public/assets/bat-ByUBN5gS.js +1 -0
  34. package/server/public/assets/beancount-BTb2W6Mp.js +1 -0
  35. package/server/public/assets/berry-5SO2uITG.js +1 -0
  36. package/server/public/assets/bibtex-CDBTNfUI.js +1 -0
  37. package/server/public/assets/bicep-fgxG_4rP.js +1 -0
  38. package/server/public/assets/bird2-BCwzDhwX.js +1 -0
  39. package/server/public/assets/blade-BBTRu2-g.js +1 -0
  40. package/server/public/assets/bsl-CG-fq4sc.js +1 -0
  41. package/server/public/assets/c-CEvNj7xl.js +1 -0
  42. package/server/public/assets/c3-Dlaci63_.js +1 -0
  43. package/server/public/assets/cadence-DHbRuEmm.js +1 -0
  44. package/server/public/assets/cairo-Ds0kTeYT.js +1 -0
  45. package/server/public/assets/catppuccin-frappe-DrL1fUuH.js +1 -0
  46. package/server/public/assets/catppuccin-latte-C0REgVjl.js +1 -0
  47. package/server/public/assets/catppuccin-macchiato-ChQpylWO.js +1 -0
  48. package/server/public/assets/catppuccin-mocha-Dd0JU1T0.js +1 -0
  49. package/server/public/assets/clarity-DMoTOm4G.js +1 -0
  50. package/server/public/assets/clojure-DBhE3PpS.js +1 -0
  51. package/server/public/assets/cmake-DwMc40Or.js +1 -0
  52. package/server/public/assets/cobol-BdNJSMRt.js +1 -0
  53. package/server/public/assets/codeowners-1lVr8wqV.js +1 -0
  54. package/server/public/assets/codeql-DWflolvo.js +1 -0
  55. package/server/public/assets/coffee-RA4xA24H.js +1 -0
  56. package/server/public/assets/common-lisp-CrVQ5xT-.js +1 -0
  57. package/server/public/assets/compiler-runtime-DZXZ41-q.js +1 -0
  58. package/server/public/assets/coq-CjfoyYSh.js +1 -0
  59. package/server/public/assets/cpp-BoW7e2Ow.js +1 -0
  60. package/server/public/assets/createServerFn-DTk395iP.js +9 -0
  61. package/server/public/assets/crystal-CYKRo3F9.js +1 -0
  62. package/server/public/assets/csharp-C7bIWP5y.js +1 -0
  63. package/server/public/assets/css-Ck2tii2d.js +1 -0
  64. package/server/public/assets/csv-DsAkDVtA.js +1 -0
  65. package/server/public/assets/cue-BWmQgbOB.js +1 -0
  66. package/server/public/assets/cypher-D-jVC50Q.js +1 -0
  67. package/server/public/assets/d-CaviyOrm.js +1 -0
  68. package/server/public/assets/dark-plus-DIrnwZt9.js +1 -0
  69. package/server/public/assets/dart-CZEi7JgC.js +1 -0
  70. package/server/public/assets/dax-BK-8zffy.js +1 -0
  71. package/server/public/assets/desktop-D3cjbL4D.js +1 -0
  72. package/server/public/assets/diff-sHAzLvlp.js +1 -0
  73. package/server/public/assets/docker--xs2Ng3w.js +1 -0
  74. package/server/public/assets/dotenv-Cm4nwcJ7.js +1 -0
  75. package/server/public/assets/dracula-CAUSusef.js +1 -0
  76. package/server/public/assets/dracula-soft-cjNkMFza.js +1 -0
  77. package/server/public/assets/dream-maker-fjmWTFCO.js +1 -0
  78. package/server/public/assets/edge-DxycC9wl.js +1 -0
  79. package/server/public/assets/elixir-B-50Er3p.js +1 -0
  80. package/server/public/assets/elm-B4-ygIVo.js +1 -0
  81. package/server/public/assets/emacs-lisp-CJzqStIa.js +1 -0
  82. package/server/public/assets/erb-DJvYE1L1.js +1 -0
  83. package/server/public/assets/erlang-C-m_88FN.js +1 -0
  84. package/server/public/assets/everforest-dark-DBpaSMx1.js +1 -0
  85. package/server/public/assets/everforest-light-CiGrXwia.js +1 -0
  86. package/server/public/assets/fennel-DRaXF7k8.js +1 -0
  87. package/server/public/assets/file-tree-CI3Xwwid.js +1907 -0
  88. package/server/public/assets/fish-Bn-Yh3Jj.js +1 -0
  89. package/server/public/assets/fluent-DF5F8Ks_.js +1 -0
  90. package/server/public/assets/fortran-fixed-form-Cx1lv7HN.js +1 -0
  91. package/server/public/assets/fortran-free-form-hCQHRqew.js +1 -0
  92. package/server/public/assets/fsharp-DC5k9sy2.js +1 -0
  93. package/server/public/assets/gdresource-D0EsKdgH.js +1 -0
  94. package/server/public/assets/gdscript-_C9_Hi_w.js +1 -0
  95. package/server/public/assets/gdshader-BW7b1X1Y.js +1 -0
  96. package/server/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  97. package/server/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  98. package/server/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  99. package/server/public/assets/genie-Ch_6TCHd.js +1 -0
  100. package/server/public/assets/gherkin-CaNUsmTq.js +1 -0
  101. package/server/public/assets/git-commit-BcFsuO5E.js +1 -0
  102. package/server/public/assets/git-rebase-ChGA-z50.js +1 -0
  103. package/server/public/assets/github-dark-B9ygjgg6.js +1 -0
  104. package/server/public/assets/github-dark-default-Br2bgYSx.js +1 -0
  105. package/server/public/assets/github-dark-dimmed-CmtqpPJ-.js +1 -0
  106. package/server/public/assets/github-dark-high-contrast-fSfmrZcC.js +1 -0
  107. package/server/public/assets/github-light-BZjUqfZl.js +1 -0
  108. package/server/public/assets/github-light-default-lIytXXhR.js +1 -0
  109. package/server/public/assets/github-light-high-contrast-BRrjFb7n.js +1 -0
  110. package/server/public/assets/gleam-DALMDpNs.js +1 -0
  111. package/server/public/assets/glimmer-js-maLb6ysA.js +1 -0
  112. package/server/public/assets/glimmer-ts-DGNr-OBA.js +1 -0
  113. package/server/public/assets/glsl-CmplqyQ1.js +1 -0
  114. package/server/public/assets/gn-DGjqrYN9.js +1 -0
  115. package/server/public/assets/gnuplot-BYckvgQI.js +1 -0
  116. package/server/public/assets/go-JycvP538.js +1 -0
  117. package/server/public/assets/graphql-VhP7n4--.js +1 -0
  118. package/server/public/assets/groovy-D5qMRONT.js +1 -0
  119. package/server/public/assets/gruvbox-dark-hard-M1dj1e6V.js +1 -0
  120. package/server/public/assets/gruvbox-dark-medium-cqq_ncQu.js +1 -0
  121. package/server/public/assets/gruvbox-dark-soft-B4QwL2a9.js +1 -0
  122. package/server/public/assets/gruvbox-light-hard-DLayMKOQ.js +1 -0
  123. package/server/public/assets/gruvbox-light-medium-D52XgPKf.js +1 -0
  124. package/server/public/assets/gruvbox-light-soft-Dola3KdD.js +1 -0
  125. package/server/public/assets/hack-BVSQ2bxM.js +1 -0
  126. package/server/public/assets/haml-CwTtRHoj.js +1 -0
  127. package/server/public/assets/handlebars-CcO01SVo.js +1 -0
  128. package/server/public/assets/haskell-ys7wPPEd.js +1 -0
  129. package/server/public/assets/haxe-94kiChn7.js +1 -0
  130. package/server/public/assets/hcl-DmHt_-wq.js +1 -0
  131. package/server/public/assets/hjson-xMmoJ0Gx.js +1 -0
  132. package/server/public/assets/hlsl-b-Pskdze.js +1 -0
  133. package/server/public/assets/horizon-BKMqttiR.js +1 -0
  134. package/server/public/assets/horizon-bright-HNkBlnm5.js +1 -0
  135. package/server/public/assets/houston-BkBSBSOQ.js +1 -0
  136. package/server/public/assets/html-derivative-Cz-cKMi2.js +1 -0
  137. package/server/public/assets/html-zQbUS8Is.js +1 -0
  138. package/server/public/assets/http-CaGQ9BgA.js +1 -0
  139. package/server/public/assets/hurl-BBoki9bg.js +1 -0
  140. package/server/public/assets/hxml-iQTOTWpM.js +1 -0
  141. package/server/public/assets/hy-DKl1XhBq.js +1 -0
  142. package/server/public/assets/imba-DPxkOTAg.js +1 -0
  143. package/server/public/assets/ini-lkLGq_1x.js +1 -0
  144. package/server/public/assets/java-LAx6oszV.js +1 -0
  145. package/server/public/assets/javascript-COqx-gKX.js +1 -0
  146. package/server/public/assets/jinja-x-G_qSCP.js +1 -0
  147. package/server/public/assets/jison-7oSeVkKJ.js +1 -0
  148. package/server/public/assets/json-sTLOVXhc.js +1 -0
  149. package/server/public/assets/json5-Cy6ypJuJ.js +1 -0
  150. package/server/public/assets/jsonc-Cw2ugYAK.js +1 -0
  151. package/server/public/assets/jsonl-Dp5_qBVH.js +1 -0
  152. package/server/public/assets/jsonnet-BTbmg_-u.js +1 -0
  153. package/server/public/assets/jssm-CnT7nPea.js +1 -0
  154. package/server/public/assets/jsx-zXeIBQLI.js +1 -0
  155. package/server/public/assets/julia-E-6Xm9nd.js +1 -0
  156. package/server/public/assets/just-D9n74gZy.js +1 -0
  157. package/server/public/assets/kanagawa-dragon-CxsBnuhV.js +1 -0
  158. package/server/public/assets/kanagawa-lotus-vHdxDDOS.js +1 -0
  159. package/server/public/assets/kanagawa-wave-CIkfTKWk.js +1 -0
  160. package/server/public/assets/kdl-BwK60g80.js +1 -0
  161. package/server/public/assets/kotlin-Dbd9Vi-v.js +1 -0
  162. package/server/public/assets/kusto-BuTk9usc.js +1 -0
  163. package/server/public/assets/laserwave-C0wf_d3o.js +1 -0
  164. package/server/public/assets/latex-D0t4RtEU.js +1 -0
  165. package/server/public/assets/lean-CYAW8bRN.js +1 -0
  166. package/server/public/assets/less-D4uen21c.js +1 -0
  167. package/server/public/assets/light-plus-oqYyWKEE.js +1 -0
  168. package/server/public/assets/liquid-BzXN12F6.js +1 -0
  169. package/server/public/assets/llvm-Z1xJzteV.js +1 -0
  170. package/server/public/assets/log-BGUxlsk3.js +1 -0
  171. package/server/public/assets/logo-wVUhvQ1b.js +1 -0
  172. package/server/public/assets/lua-B0Cg8RP4.js +1 -0
  173. package/server/public/assets/luau-rPFZzCmq.js +1 -0
  174. package/server/public/assets/main-FvxVz-kD.js +15 -0
  175. package/server/public/assets/make-BmPf6m0P.js +1 -0
  176. package/server/public/assets/markdown-AseU6zcW.js +1 -0
  177. package/server/public/assets/marko-BlRPXWOe.js +1 -0
  178. package/server/public/assets/material-theme-6_W6rQhR.js +1 -0
  179. package/server/public/assets/material-theme-darker-VPEo3Sem.js +1 -0
  180. package/server/public/assets/material-theme-lighter-CUhzCcZ9.js +1 -0
  181. package/server/public/assets/material-theme-ocean-B2JdsaGb.js +1 -0
  182. package/server/public/assets/material-theme-palenight-DhY-sklA.js +1 -0
  183. package/server/public/assets/matlab-BOj_BDQv.js +1 -0
  184. package/server/public/assets/mdc-FiVDZSZ4.js +1 -0
  185. package/server/public/assets/mdx-Cm6cDkDI.js +1 -0
  186. package/server/public/assets/mermaid-DLO-R4hv.js +1 -0
  187. package/server/public/assets/min-dark-D34a_pX7.js +1 -0
  188. package/server/public/assets/min-light-Cdd4KORE.js +1 -0
  189. package/server/public/assets/mipsasm-DYpHF-GA.js +1 -0
  190. package/server/public/assets/mojo-DqYVFv_G.js +1 -0
  191. package/server/public/assets/monokai-CDR4sQ2n.js +1 -0
  192. package/server/public/assets/moonbit-DRKee9wk.js +1 -0
  193. package/server/public/assets/move-DbRk6Vn9.js +1 -0
  194. package/server/public/assets/narrat-IOfmaXfb.js +1 -0
  195. package/server/public/assets/new-DOyplRwM.js +1 -0
  196. package/server/public/assets/nextflow-D-Ec_bsY.js +1 -0
  197. package/server/public/assets/nextflow-groovy-EYl0c2BQ.js +1 -0
  198. package/server/public/assets/nginx-3JLAqmJa.js +1 -0
  199. package/server/public/assets/night-owl-light-Bedht9b4.js +1 -0
  200. package/server/public/assets/night-owl-yQJ3-I0I.js +1 -0
  201. package/server/public/assets/nim-DyjFVMzT.js +1 -0
  202. package/server/public/assets/nix-C2IovEl2.js +1 -0
  203. package/server/public/assets/nord-BjZ63GNL.js +1 -0
  204. package/server/public/assets/nushell-BflTrRB5.js +1 -0
  205. package/server/public/assets/objective-c-GRClK1S7.js +1 -0
  206. package/server/public/assets/objective-cpp-l3qYw-v5.js +1 -0
  207. package/server/public/assets/ocaml-BBDyhyMH.js +1 -0
  208. package/server/public/assets/odin-jCJ7Js99.js +1 -0
  209. package/server/public/assets/one-dark-pro-PIx2Diul.js +1 -0
  210. package/server/public/assets/one-light-BFMEz49S.js +1 -0
  211. package/server/public/assets/openscad-Drf0LgCX.js +1 -0
  212. package/server/public/assets/pascal-BT2XAUTl.js +1 -0
  213. package/server/public/assets/perl-Dr47G_2Q.js +1 -0
  214. package/server/public/assets/php-BhBDWTJe.js +1 -0
  215. package/server/public/assets/pierre-dark-CTXzTLfO.js +1 -0
  216. package/server/public/assets/pierre-light-C_5rlJRo.js +1 -0
  217. package/server/public/assets/pkl-ML-dWShO.js +1 -0
  218. package/server/public/assets/plastic-BFI-Z5Z2.js +1 -0
  219. package/server/public/assets/plsql-0vd5cLro.js +1 -0
  220. package/server/public/assets/po-CbZ_uqQA.js +1 -0
  221. package/server/public/assets/poimandres-Cayhd01L.js +1 -0
  222. package/server/public/assets/polar-C4hfV8Nc.js +1 -0
  223. package/server/public/assets/postcss-osFUbTLw.js +1 -0
  224. package/server/public/assets/powerquery-CTlGUQPj.js +1 -0
  225. package/server/public/assets/powershell-DyZsOmuq.js +1 -0
  226. package/server/public/assets/preload-helper-D7oT-Xwl.js +20 -0
  227. package/server/public/assets/prisma-SS92PO_I.js +1 -0
  228. package/server/public/assets/prolog-B1O1NNVC.js +1 -0
  229. package/server/public/assets/proto-BWu3eZTs.js +1 -0
  230. package/server/public/assets/pug-Dij_IK5w.js +1 -0
  231. package/server/public/assets/puppet-tvtRVdr6.js +1 -0
  232. package/server/public/assets/purescript-Dtbpb7D-.js +1 -0
  233. package/server/public/assets/python-Dlk0Acio.js +1 -0
  234. package/server/public/assets/qml-qUwk3nhh.js +1 -0
  235. package/server/public/assets/qmldir-B-iEOngH.js +1 -0
  236. package/server/public/assets/qss-Ba0p-aHw.js +1 -0
  237. package/server/public/assets/r-WmtNicKM.js +1 -0
  238. package/server/public/assets/racket-BXDsxf2U.js +1 -0
  239. package/server/public/assets/raku-Dw1WWFXK.js +1 -0
  240. package/server/public/assets/razor-DaqiVx3Q.js +1 -0
  241. package/server/public/assets/red-BC3Ds49b.js +1 -0
  242. package/server/public/assets/reg-DXFHGaM4.js +1 -0
  243. package/server/public/assets/regexp-CiSWN5Ne.js +1 -0
  244. package/server/public/assets/rel-Dc5_Ytx2.js +1 -0
  245. package/server/public/assets/reviews-CJvVXRLH.js +1 -0
  246. package/server/public/assets/reviews-CfbuF6ib.js +1 -0
  247. package/server/public/assets/riscv-ZgswiWij.js +1 -0
  248. package/server/public/assets/ron-YghabWAH.js +1 -0
  249. package/server/public/assets/rose-pine-ByWLnVr3.js +1 -0
  250. package/server/public/assets/rose-pine-dawn-DBmeySrz.js +1 -0
  251. package/server/public/assets/rose-pine-moon-B9J-N3nK.js +1 -0
  252. package/server/public/assets/rosmsg-DTKmAsVH.js +1 -0
  253. package/server/public/assets/routes-DNxq1Fba.js +1 -0
  254. package/server/public/assets/routes-Dp0ODZ55.js +2 -0
  255. package/server/public/assets/rst-CP6xOYlY.js +1 -0
  256. package/server/public/assets/ruby-BXYLc1CM.js +1 -0
  257. package/server/public/assets/rust-nfXwuE6F.js +1 -0
  258. package/server/public/assets/sas-CFdtZutF.js +1 -0
  259. package/server/public/assets/sass-CbRjkld3.js +1 -0
  260. package/server/public/assets/scala-BzD3eypx.js +1 -0
  261. package/server/public/assets/scheme-D4d1PV1y.js +1 -0
  262. package/server/public/assets/scss-D8KhdObH.js +1 -0
  263. package/server/public/assets/sdbl-Cf-Ydnvx.js +1 -0
  264. package/server/public/assets/shaderlab-DGohHMiF.js +1 -0
  265. package/server/public/assets/shellscript-BHdEbumI.js +1 -0
  266. package/server/public/assets/shellsession-Dh-bxrap.js +1 -0
  267. package/server/public/assets/slack-dark-MszIyPZ2.js +1 -0
  268. package/server/public/assets/slack-ochin-tQ3Q0gE9.js +1 -0
  269. package/server/public/assets/smalltalk-_uWoArwn.js +1 -0
  270. package/server/public/assets/snazzy-light-9sniMEk5.js +1 -0
  271. package/server/public/assets/solarized-dark-CdD0Hxzv.js +1 -0
  272. package/server/public/assets/solarized-light-C-nsEdqF.js +1 -0
  273. package/server/public/assets/solidity-D6uC-xwP.js +1 -0
  274. package/server/public/assets/soy-cDuODfbT.js +1 -0
  275. package/server/public/assets/sparql-BgU2QITA.js +1 -0
  276. package/server/public/assets/splunk-LQYHRu14.js +1 -0
  277. package/server/public/assets/sql-CKZpK620.js +1 -0
  278. package/server/public/assets/ssh-config-B7BUl8Rd.js +1 -0
  279. package/server/public/assets/stata-BLJTbKOO.js +1 -0
  280. package/server/public/assets/styles-UDowwF7S.css +2 -0
  281. package/server/public/assets/stylus-Byjxdx_q.js +1 -0
  282. package/server/public/assets/surrealql-C96KvYaj.js +1 -0
  283. package/server/public/assets/svelte-Qnbj2GWx.js +1 -0
  284. package/server/public/assets/swift-BexLlMrU.js +1 -0
  285. package/server/public/assets/synthwave-84-BxMBwQMS.js +1 -0
  286. package/server/public/assets/system-verilog-DVGwm0mw.js +1 -0
  287. package/server/public/assets/systemd-H2IT3-p5.js +1 -0
  288. package/server/public/assets/talonscript-mKZIGM8n.js +1 -0
  289. package/server/public/assets/tasl-B7he_Ugr.js +1 -0
  290. package/server/public/assets/tcl-5mT3RxHH.js +1 -0
  291. package/server/public/assets/templ-CQPDll3D.js +1 -0
  292. package/server/public/assets/terraform-BZP0GLsT.js +1 -0
  293. package/server/public/assets/test-D7JRfog1.js +1 -0
  294. package/server/public/assets/tex-97QNLoBJ.js +1 -0
  295. package/server/public/assets/tokyo-night-CTPVdZt9.js +1 -0
  296. package/server/public/assets/toml-CTFA98he.js +1 -0
  297. package/server/public/assets/ts-tags-B8zlXe2n.js +1 -0
  298. package/server/public/assets/tsv-BayJtYdY.js +1 -0
  299. package/server/public/assets/tsx-VqRU8NCz.js +1 -0
  300. package/server/public/assets/turtle-TVCBh_kY.js +1 -0
  301. package/server/public/assets/twig-FTTF8rVk.js +1 -0
  302. package/server/public/assets/typescript-CuX0hIVY.js +1 -0
  303. package/server/public/assets/typespec-BUvaJDLF.js +1 -0
  304. package/server/public/assets/typst-8NBaY7Ec.js +1 -0
  305. package/server/public/assets/useStore-M3H8PB1v.js +1 -0
  306. package/server/public/assets/utils-DElCu2hq.js +1 -0
  307. package/server/public/assets/v-VihyTigi.js +1 -0
  308. package/server/public/assets/vala-DyFAPyX6.js +1 -0
  309. package/server/public/assets/vb-Dg1Iqi4J.js +1 -0
  310. package/server/public/assets/verilog-D2Xc-vhD.js +1 -0
  311. package/server/public/assets/vesper-DJbtqYNr.js +1 -0
  312. package/server/public/assets/vhdl-CU3BVeE7.js +1 -0
  313. package/server/public/assets/viml-hG2shuOW.js +1 -0
  314. package/server/public/assets/vitesse-black-DbG2gsc0.js +1 -0
  315. package/server/public/assets/vitesse-dark-B6WV4xXH.js +1 -0
  316. package/server/public/assets/vitesse-light-DC1pdD02.js +1 -0
  317. package/server/public/assets/vue-DXwaEU0U.js +1 -0
  318. package/server/public/assets/vue-html-QD7AJ6JJ.js +1 -0
  319. package/server/public/assets/vue-vine-Bh2m1D2Z.js +1 -0
  320. package/server/public/assets/vyper-C1wojIuk.js +1 -0
  321. package/server/public/assets/wasm-C6Y0s02M.js +1 -0
  322. package/server/public/assets/wasm-qTvCOSHz.js +1 -0
  323. package/server/public/assets/wenyan-BG5vPQF0.js +1 -0
  324. package/server/public/assets/wgsl-DrVb-Cub.js +1 -0
  325. package/server/public/assets/wikitext-PRC4s8sH.js +1 -0
  326. package/server/public/assets/wit-ChW5qvg_.js +1 -0
  327. package/server/public/assets/wolfram-B8mKuZSQ.js +1 -0
  328. package/server/public/assets/xml-BK-rcb5a.js +1 -0
  329. package/server/public/assets/xsl-dt-d2R7p.js +1 -0
  330. package/server/public/assets/yaml-UiXU3hGj.js +1 -0
  331. package/server/public/assets/zenscript-C-jEPC9j.js +1 -0
  332. package/server/public/assets/zig-EbnRGjcz.js +1 -0
  333. package/server/public/favicon.ico +0 -0
  334. package/server/public/logo192.png +0 -0
  335. package/server/public/logo512.png +0 -0
  336. package/server/public/manifest.json +25 -0
  337. package/server/public/robots.txt +3 -0
  338. package/server/public/tanstack-circle-logo.png +0 -0
  339. package/server/public/tanstack-word-logo-white.svg +1 -0
  340. package/server/server/_chunks/ssr-renderer.mjs +15 -0
  341. package/server/server/_libs/@floating-ui/core+[...].mjs +698 -0
  342. package/server/server/_libs/@floating-ui/dom+[...].mjs +644 -0
  343. package/server/server/_libs/@floating-ui/react-dom+[...].mjs +839 -0
  344. package/server/server/_libs/@pierre/diffs+[...].mjs +18578 -0
  345. package/server/server/_libs/@radix-ui/react-arrow+[...].mjs +174 -0
  346. package/server/server/_libs/@radix-ui/react-collection+[...].mjs +162 -0
  347. package/server/server/_libs/@radix-ui/react-dialog+[...].mjs +1666 -0
  348. package/server/server/_libs/@radix-ui/react-popper+[...].mjs +289 -0
  349. package/server/server/_libs/@radix-ui/react-radio-group+[...].mjs +420 -0
  350. package/server/server/_libs/@radix-ui/react-select+[...].mjs +990 -0
  351. package/server/server/_libs/@tanstack/react-router+[...].mjs +14113 -0
  352. package/server/server/_libs/_.mjs +2 -0
  353. package/server/server/_libs/chokidar+readdirp.mjs +1599 -0
  354. package/server/server/_libs/class-variance-authority+clsx.mjs +69 -0
  355. package/server/server/_libs/effect+[...].mjs +34047 -0
  356. package/server/server/_libs/h3+rou3+srvx.mjs +1195 -0
  357. package/server/server/_libs/hookable.mjs +41 -0
  358. package/server/server/_libs/lucide-react.mjs +298 -0
  359. package/server/server/_libs/pierre__theme.mjs +2668 -0
  360. package/server/server/_libs/radix-ui__number.mjs +6 -0
  361. package/server/server/_libs/radix-ui__primitive.mjs +9 -0
  362. package/server/server/_libs/radix-ui__react-direction.mjs +11 -0
  363. package/server/server/_libs/shiki.mjs +16 -0
  364. package/server/server/_libs/shikijs__langs.mjs +1355 -0
  365. package/server/server/_libs/shikijs__themes.mjs +262 -0
  366. package/server/server/_libs/tailwind-merge.mjs +1962 -0
  367. package/server/server/_libs/tanstack__history.mjs +342 -0
  368. package/server/server/_libs/tanstack__router-core.mjs +6 -0
  369. package/server/server/_libs/ufo.mjs +64 -0
  370. package/server/server/_reviewId-AWnOGz5k.mjs +33 -0
  371. package/server/server/_reviewId-Com4yOlc.mjs +29 -0
  372. package/server/server/_reviewId-DAhmekJ2.mjs +277 -0
  373. package/server/server/_reviewId-p9mhYVwa.mjs +18 -0
  374. package/server/server/_runtime.mjs +35 -0
  375. package/server/server/_ssr/action-bar-C68xGnWW.mjs +592 -0
  376. package/server/server/_ssr/api-handler-CstW2n82.mjs +189 -0
  377. package/server/server/_ssr/client-runtime-BoPuAEoA.mjs +245 -0
  378. package/server/server/_ssr/createServerRpc--0mcGlWK.mjs +12 -0
  379. package/server/server/_ssr/createSsrRpc-AwdiLXmF.mjs +16 -0
  380. package/server/server/_ssr/domain-rpc-3Ds9DPr0.mjs +287 -0
  381. package/server/server/_ssr/file-tree-CQ5w2GHh.mjs +1951 -0
  382. package/server/server/_ssr/load-scoped-diff-NL2XAcdz.mjs +45 -0
  383. package/server/server/_ssr/new-BKl_G2Ks.mjs +37 -0
  384. package/server/server/_ssr/new-BREdMFAM.mjs +12 -0
  385. package/server/server/_ssr/new-DCz5eHkb.mjs +137 -0
  386. package/server/server/_ssr/reviews-BL5Nsgst.mjs +7 -0
  387. package/server/server/_ssr/reviews-BoaEgGKs.mjs +100 -0
  388. package/server/server/_ssr/reviews-C7_NIhY8.mjs +19 -0
  389. package/server/server/_ssr/reviews-Dd69YBDa.mjs +12 -0
  390. package/server/server/_ssr/router-DLxN8FOm.mjs +415 -0
  391. package/server/server/_ssr/routes-D25G8OuS.mjs +80 -0
  392. package/server/server/_ssr/routes-lz0AN75A.mjs +929 -0
  393. package/server/server/_ssr/runtime-D9IbnMlF.mjs +1401 -0
  394. package/server/server/_ssr/server-runtime-D99qpmma.mjs +12 -0
  395. package/server/server/_ssr/ssr.mjs +5318 -0
  396. package/server/server/_ssr/start-BIQfOZtj.mjs +4 -0
  397. package/server/server/_ssr/test-CQdMYlqa.mjs +6 -0
  398. package/server/server/_ssr/todo-m_uUvxca.mjs +88 -0
  399. package/server/server/_ssr/use-keyboard-shortcuts-D5b1Mxpq.mjs +25 -0
  400. package/server/server/_ssr/utils-BuOt9_LA.mjs +8 -0
  401. package/server/server/_tanstack-start-manifest_v-CnL10NRH.mjs +71 -0
  402. package/server/server/index.mjs +2615 -0
  403. package/server/server/node_modules/detect-libc/lib/detect-libc.js +313 -0
  404. package/server/server/node_modules/detect-libc/lib/elf.js +39 -0
  405. package/server/server/node_modules/detect-libc/lib/filesystem.js +51 -0
  406. package/server/server/node_modules/detect-libc/lib/process.js +24 -0
  407. package/server/server/node_modules/detect-libc/package.json +44 -0
  408. package/server/server/node_modules/msgpackr-extract/index.js +1 -0
  409. package/server/server/node_modules/msgpackr-extract/package.json +50 -0
  410. package/server/server/node_modules/node-gyp-build-optional-packages/index.js +6 -0
  411. package/server/server/node_modules/node-gyp-build-optional-packages/node-gyp-build.js +236 -0
  412. package/server/server/node_modules/node-gyp-build-optional-packages/package.json +32 -0
  413. package/server/server/node_modules/tslib/modules/index.js +70 -0
  414. package/server/server/node_modules/tslib/modules/package.json +3 -0
  415. package/server/server/node_modules/tslib/package.json +47 -0
  416. package/server/server/node_modules/tslib/tslib.js +484 -0
  417. package/server/server/package.json +12 -0
  418. package/dist/chunk-KMYSGMD3.js +0 -3526
  419. package/dist/chunk-KMYSGMD3.js.map +0 -1
  420. package/dist/cli.js +0 -1839
  421. package/dist/cli.js.map +0 -1
  422. package/dist/mcp.js +0 -1228
  423. package/dist/mcp.js.map +0 -1
@@ -0,0 +1,1951 @@
1
+ import { i as __toESM } from "../_runtime.mjs";
2
+ import { u as require_react } from "../_libs/@floating-ui/react-dom+[...].mjs";
3
+ import { p as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
4
+ import { n as require_jsx_runtime, t as PatchDiff } from "../_libs/@pierre/diffs+[...].mjs";
5
+ import { Ct as tap, It as hasInterruptsOnly, Lt as pretty, N as interrupt, St as sync, Tt as timeout, ct as ensuring, dt as gen, gt as matchCauseEffect, kt as withSpan, vt as promise, wt as tapCause } from "../_libs/effect+[...].mjs";
6
+ import { i as clientRuntime, t as ApiClient } from "./client-runtime-BoPuAEoA.mjs";
7
+ import { t as cn } from "./utils-BuOt9_LA.mjs";
8
+ import { b as Check, c as Folder, f as Download, l as FolderOpen, p as Clipboard, u as File, v as ChevronRight } from "../_libs/lucide-react.mjs";
9
+ import { a as DialogFooter, i as DialogDescription, n as Dialog$1, o as DialogHeader, r as DialogContent, s as DialogTitle } from "./action-bar-C68xGnWW.mjs";
10
+ //#region node_modules/.nitro/vite/services/ssr/assets/file-tree-CQ5w2GHh.js
11
+ var import_react = /* @__PURE__ */ __toESM(require_react());
12
+ var import_jsx_runtime = require_jsx_runtime();
13
+ var LINE_TYPE_LABELS = {
14
+ added: "new",
15
+ context: "context",
16
+ removed: "old"
17
+ };
18
+ var lineTypeLabel = (type) => (type && LINE_TYPE_LABELS[type]) ?? "context";
19
+ var groupByFile = (comments) => {
20
+ const groups = /* @__PURE__ */ new Map();
21
+ for (const comment of comments) {
22
+ const key = comment.filePath || "(general)";
23
+ const existing = groups.get(key);
24
+ if (existing) existing.push(comment);
25
+ else groups.set(key, [comment]);
26
+ }
27
+ return groups;
28
+ };
29
+ var sortByLine = (a, b) => (a.lineNumber ?? 0) - (b.lineNumber ?? 0);
30
+ var formatReviewFeedback = (comments) => {
31
+ if (comments.length === 0) return "";
32
+ const lines = ["# Code Review Feedback", ""];
33
+ const groups = groupByFile(comments);
34
+ const sortedFiles = [...groups.keys()].toSorted((a, b) => a.localeCompare(b));
35
+ for (const filePath of sortedFiles) {
36
+ const fileComments = groups.get(filePath);
37
+ if (!fileComments || fileComments.length === 0) continue;
38
+ fileComments.sort(sortByLine);
39
+ lines.push(`## ${filePath}`, "");
40
+ for (const comment of fileComments) {
41
+ const lineLabel = comment.lineNumber === null || comment.lineNumber === void 0 ? "General" : `Line ${comment.lineNumber} (${lineTypeLabel(comment.lineType)})`;
42
+ lines.push(`### ${lineLabel}`);
43
+ lines.push(comment.content);
44
+ if (comment.suggestion) {
45
+ lines.push("");
46
+ lines.push("```suggestion");
47
+ lines.push(comment.suggestion);
48
+ lines.push("```");
49
+ }
50
+ lines.push("");
51
+ }
52
+ }
53
+ return `${lines.join("\n").trimEnd()}\n`;
54
+ };
55
+ var COPY_FEEDBACK_MS = 1800;
56
+ var EMPTY_API_COMMENTS = [];
57
+ var EMPTY_LOCAL_COMMENTS = [];
58
+ var buttonMotionClass = "transition-[transform,background-color,color,border-color,box-shadow,opacity] duration-150 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] motion-reduce:transform-none";
59
+ var EmptyState = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
60
+ className: "flex flex-col items-center justify-center gap-2 py-12",
61
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Clipboard, { className: "size-4 text-text-tertiary" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
62
+ className: "text-sm text-text-tertiary",
63
+ children: "No comments to export"
64
+ })]
65
+ });
66
+ var MarkdownPreview = ({ markdown }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
67
+ className: "relative min-h-0 flex-1 overflow-hidden rounded-lg border border-border-default bg-surface-inset/40",
68
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
69
+ className: "h-full overflow-y-auto whitespace-pre-wrap break-words p-4 font-mono text-xs leading-relaxed text-text-secondary selection:bg-accent-muted",
70
+ children: markdown
71
+ })
72
+ });
73
+ var CopyButton = ({ copied, disabled, onClick }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
74
+ type: "button",
75
+ disabled,
76
+ onClick,
77
+ className: cn("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium active:scale-[0.97]", buttonMotionClass, copied ? "bg-status-success/15 text-status-success" : "bg-accent-primary text-white hover:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40 disabled:cursor-not-allowed disabled:opacity-40"),
78
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
79
+ className: cn("inline-flex transition-[transform,opacity] duration-150 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", copied ? "scale-110" : "scale-100"),
80
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Clipboard, { className: "size-3.5" })
81
+ }), copied ? "Copied!" : "Copy to Clipboard"]
82
+ });
83
+ var DownloadButton = ({ disabled, onClick }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
84
+ type: "button",
85
+ disabled,
86
+ onClick,
87
+ className: cn("inline-flex items-center gap-1.5 rounded-lg border border-border-default bg-surface-elevated px-3 py-1.5 text-xs text-text-secondary hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 active:scale-[0.97] disabled:cursor-not-allowed disabled:opacity-40", buttonMotionClass),
88
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "size-3.5" }), "Download .md"]
89
+ });
90
+ var ExportFeedbackModal = ({ open, onOpenChange, comments = EMPTY_API_COMMENTS, localComments = EMPTY_LOCAL_COMMENTS, reviewId }) => {
91
+ const [copied, setCopied] = (0, import_react.useState)(false);
92
+ const timerRef = (0, import_react.useRef)(null);
93
+ (0, import_react.useEffect)(() => {
94
+ if (!open) setCopied(false);
95
+ return () => {
96
+ if (timerRef.current) clearTimeout(timerRef.current);
97
+ };
98
+ }, [open]);
99
+ const allComments = (0, import_react.useMemo)(() => {
100
+ return [...comments.map((c) => ({
101
+ content: c.content,
102
+ filePath: c.filePath,
103
+ lineNumber: c.lineNumber,
104
+ lineType: c.lineType,
105
+ suggestion: c.suggestion
106
+ })), ...localComments];
107
+ }, [comments, localComments]);
108
+ const markdown = (0, import_react.useMemo)(() => formatReviewFeedback(allComments), [allComments]);
109
+ const commentCount = allComments.length;
110
+ const handleCopy = (0, import_react.useCallback)(async () => {
111
+ if (!markdown) return;
112
+ await navigator.clipboard.writeText(markdown);
113
+ setCopied(true);
114
+ timerRef.current = setTimeout(() => setCopied(false), COPY_FEEDBACK_MS);
115
+ }, [markdown]);
116
+ const handleDownload = (0, import_react.useCallback)(() => {
117
+ if (!markdown) return;
118
+ const blob = new Blob([markdown], { type: "text/markdown" });
119
+ const url = URL.createObjectURL(blob);
120
+ const anchor = document.createElement("a");
121
+ anchor.href = url;
122
+ anchor.download = reviewId ? `review-${reviewId}.md` : "review-feedback.md";
123
+ anchor.click();
124
+ URL.revokeObjectURL(url);
125
+ }, [markdown, reviewId]);
126
+ const isEmpty = commentCount === 0;
127
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
128
+ open,
129
+ onOpenChange,
130
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
131
+ className: "flex max-h-[min(640px,calc(100dvh-2rem))] w-full max-w-[calc(100%-1.5rem)] flex-col gap-0 overflow-hidden sm:max-w-2xl",
132
+ children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Export Feedback" }), isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: "No comments to export." }) : null] }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
135
+ className: "flex min-h-0 flex-1 flex-col px-5 py-4",
136
+ children: isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyState, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownPreview, { markdown })
137
+ }),
138
+ isEmpty ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DownloadButton, {
139
+ disabled: isEmpty,
140
+ onClick: handleDownload
141
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
142
+ copied,
143
+ disabled: isEmpty,
144
+ onClick: handleCopy
145
+ })] })
146
+ ]
147
+ })
148
+ });
149
+ };
150
+ function DiffSummary({ summary }) {
151
+ if (summary.totalAdditions === 0 && summary.totalDeletions === 0) return null;
152
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153
+ className: "flex items-center gap-2 text-xs tabular-nums",
154
+ children: [summary.totalAdditions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
155
+ className: "text-diff-add-text",
156
+ children: ["+", summary.totalAdditions]
157
+ }) : null, summary.totalDeletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
158
+ className: "text-diff-remove-text",
159
+ children: ["-", summary.totalDeletions]
160
+ }) : null]
161
+ });
162
+ }
163
+ var STORAGE_KEY = "ringi:split-diff-ratio";
164
+ var DEFAULT_RATIO = .5;
165
+ var MIN_RATIO = .2;
166
+ var MAX_RATIO = .8;
167
+ var KEYBOARD_STEP = .01;
168
+ var KEYBOARD_STEP_LARGE = .05;
169
+ /**
170
+ * CSS override injected once into the pierre/diffs shadow root.
171
+ * CSS custom properties set on the host element inherit into shadow DOM,
172
+ * so `--ringi-split-left` / `--ringi-split-right` are readable here.
173
+ */
174
+ var SHADOW_STYLE_ID = "ringi-split-override";
175
+ var SHADOW_CSS = `
176
+ [data-diff-type='split'][data-overflow='scroll'] {
177
+ grid-template-columns: var(--ringi-split-left, 1fr) var(--ringi-split-right, 1fr) !important;
178
+ }
179
+ [data-diff-type='split'][data-overflow='wrap'] {
180
+ grid-template-columns:
181
+ var(--diffs-grid-number-column-width)
182
+ var(--ringi-split-left, 1fr)
183
+ var(--diffs-grid-number-column-width)
184
+ var(--ringi-split-right, 1fr) !important;
185
+ }
186
+ `;
187
+ var clampRatio = (r) => Math.min(MAX_RATIO, Math.max(MIN_RATIO, r));
188
+ var loadRatio = () => {
189
+ try {
190
+ const stored = localStorage.getItem(STORAGE_KEY);
191
+ if (stored !== null) {
192
+ const parsed = Number.parseFloat(stored);
193
+ if (Number.isFinite(parsed)) return clampRatio(parsed);
194
+ }
195
+ } catch {}
196
+ return DEFAULT_RATIO;
197
+ };
198
+ var saveRatio = (ratio) => {
199
+ try {
200
+ localStorage.setItem(STORAGE_KEY, ratio.toFixed(4));
201
+ } catch {}
202
+ };
203
+ /**
204
+ * Injects the CSS override into the shadow root of a `diffs-container` element.
205
+ * Returns `true` if injection succeeded.
206
+ */
207
+ var injectShadowStyle = (container) => {
208
+ const shadow = container.shadowRoot;
209
+ if (!shadow) return false;
210
+ if (shadow.querySelector(`#${SHADOW_STYLE_ID}`)) return true;
211
+ const style = document.createElement("style");
212
+ style.id = SHADOW_STYLE_ID;
213
+ style.textContent = SHADOW_CSS;
214
+ shadow.append(style);
215
+ return true;
216
+ };
217
+ /**
218
+ * Sets the CSS custom properties on the `diffs-container` host element.
219
+ * These inherit into the shadow DOM where our injected CSS rule reads them.
220
+ */
221
+ var applySplitRatio = (container, ratio) => {
222
+ container.style.setProperty("--ringi-split-left", `${ratio}fr`);
223
+ container.style.setProperty("--ringi-split-right", `${1 - ratio}fr`);
224
+ };
225
+ var useSplitDiffResizer = (enabled) => {
226
+ const [ratio, setRatioState] = (0, import_react.useState)(loadRatio);
227
+ const [isDragging, setIsDragging] = (0, import_react.useState)(false);
228
+ const wrapperElRef = (0, import_react.useRef)(null);
229
+ const diffsContainerRef = (0, import_react.useRef)(null);
230
+ const ratioRef = (0, import_react.useRef)(ratio);
231
+ const draggingRef = (0, import_react.useRef)(false);
232
+ ratioRef.current = ratio;
233
+ const setRatio = (0, import_react.useCallback)((next) => {
234
+ const clamped = clampRatio(next);
235
+ ratioRef.current = clamped;
236
+ setRatioState(clamped);
237
+ saveRatio(clamped);
238
+ if (diffsContainerRef.current) applySplitRatio(diffsContainerRef.current, clamped);
239
+ }, []);
240
+ const setupDiffsContainer = (0, import_react.useCallback)((wrapper) => {
241
+ if (!wrapper || !enabled) return;
242
+ const tryInject = () => {
243
+ const container = wrapper.querySelector("diffs-container");
244
+ if (!container) return false;
245
+ const el = container;
246
+ diffsContainerRef.current = el;
247
+ const ok = injectShadowStyle(el);
248
+ if (ok) applySplitRatio(el, ratioRef.current);
249
+ return ok;
250
+ };
251
+ if (tryInject()) return;
252
+ const observer = new MutationObserver(() => {
253
+ if (tryInject()) observer.disconnect();
254
+ });
255
+ observer.observe(wrapper, {
256
+ childList: true,
257
+ subtree: true
258
+ });
259
+ const timeout = setTimeout(() => observer.disconnect(), 5e3);
260
+ return () => {
261
+ observer.disconnect();
262
+ clearTimeout(timeout);
263
+ };
264
+ }, [enabled]);
265
+ const wrapperRef = (0, import_react.useCallback)((node) => {
266
+ wrapperElRef.current = node;
267
+ setupDiffsContainer(node);
268
+ }, [setupDiffsContainer]);
269
+ (0, import_react.useEffect)(() => {
270
+ if (diffsContainerRef.current && enabled) {
271
+ injectShadowStyle(diffsContainerRef.current);
272
+ applySplitRatio(diffsContainerRef.current, ratio);
273
+ }
274
+ }, [ratio, enabled]);
275
+ const handlePointerDown = (0, import_react.useCallback)((e) => {
276
+ if (!enabled || !wrapperElRef.current) return;
277
+ e.preventDefault();
278
+ e.currentTarget.setPointerCapture(e.pointerId);
279
+ draggingRef.current = true;
280
+ setIsDragging(true);
281
+ document.body.style.userSelect = "none";
282
+ document.body.style.cursor = "col-resize";
283
+ const wrapperRect = wrapperElRef.current.getBoundingClientRect();
284
+ const onPointerMove = (ev) => {
285
+ if (!draggingRef.current) return;
286
+ const newRatio = clampRatio((ev.clientX - wrapperRect.left) / wrapperRect.width);
287
+ ratioRef.current = newRatio;
288
+ if (diffsContainerRef.current) applySplitRatio(diffsContainerRef.current, newRatio);
289
+ if (wrapperElRef.current) wrapperElRef.current.style.setProperty("--ringi-splitter-left", `${newRatio * 100}%`);
290
+ };
291
+ const onPointerUp = () => {
292
+ draggingRef.current = false;
293
+ setIsDragging(false);
294
+ document.body.style.userSelect = "";
295
+ document.body.style.cursor = "";
296
+ setRatio(ratioRef.current);
297
+ document.removeEventListener("pointermove", onPointerMove);
298
+ document.removeEventListener("pointerup", onPointerUp);
299
+ };
300
+ document.addEventListener("pointermove", onPointerMove);
301
+ document.addEventListener("pointerup", onPointerUp);
302
+ }, [enabled, setRatio]);
303
+ const handleKeyDown = (0, import_react.useCallback)((e) => {
304
+ if (!enabled) return;
305
+ const step = e.shiftKey ? KEYBOARD_STEP_LARGE : KEYBOARD_STEP;
306
+ let handled = false;
307
+ if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
308
+ setRatio(ratioRef.current - step);
309
+ handled = true;
310
+ } else if (e.key === "ArrowRight" || e.key === "ArrowUp") {
311
+ setRatio(ratioRef.current + step);
312
+ handled = true;
313
+ } else if (e.key === "Home") {
314
+ setRatio(MIN_RATIO);
315
+ handled = true;
316
+ } else if (e.key === "End") {
317
+ setRatio(MAX_RATIO);
318
+ handled = true;
319
+ } else if (e.key === "Enter" || e.key === " ") {
320
+ setRatio(DEFAULT_RATIO);
321
+ handled = true;
322
+ }
323
+ if (handled) {
324
+ e.preventDefault();
325
+ e.stopPropagation();
326
+ }
327
+ }, [enabled, setRatio]);
328
+ const handleDoubleClick = (0, import_react.useCallback)(() => {
329
+ setRatio(DEFAULT_RATIO);
330
+ }, [setRatio]);
331
+ return {
332
+ isDragging,
333
+ ratio,
334
+ splitterProps: {
335
+ "aria-label": "Resize diff panes",
336
+ "aria-orientation": "vertical",
337
+ "aria-valuemax": MAX_RATIO * 100,
338
+ "aria-valuemin": MIN_RATIO * 100,
339
+ "aria-valuenow": Math.round(ratio * 100),
340
+ onDoubleClick: handleDoubleClick,
341
+ onKeyDown: handleKeyDown,
342
+ onPointerDown: handlePointerDown,
343
+ role: "separator",
344
+ tabIndex: 0
345
+ },
346
+ wrapperRef
347
+ };
348
+ };
349
+ /**
350
+ * Wraps a PatchDiff (split mode) and overlays a draggable vertical splitter.
351
+ *
352
+ * Architecture:
353
+ * - The wrapper div receives a ref that discovers the `<diffs-container>`
354
+ * shadow DOM and injects a CSS override so the split grid columns read
355
+ * `--ringi-split-left` / `--ringi-split-right` CSS variables.
356
+ * - The splitter handle is absolutely positioned at the split point.
357
+ * - During drag, DOM updates happen via direct style manipulation (no React
358
+ * re-renders), then the final ratio is committed to state + localStorage.
359
+ * - Keyboard: Arrow keys adjust ratio, Enter/Space resets to 50%.
360
+ */
361
+ var SplitDiffSplitter = ({ children, enabled = true }) => {
362
+ const { ratio, isDragging, wrapperRef, splitterProps } = useSplitDiffResizer(enabled);
363
+ if (!enabled) return children;
364
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
365
+ ref: wrapperRef,
366
+ className: "ringi-split-diff-wrapper relative",
367
+ style: { "--ringi-splitter-left": `${ratio * 100}%` },
368
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
369
+ ...splitterProps,
370
+ className: cn("ringi-split-splitter group/splitter", "absolute top-0 bottom-0 z-10", "-translate-x-1/2", "flex items-center justify-center", "cursor-col-resize", "outline-none", isDragging && "ringi-split-splitter--active"),
371
+ style: {
372
+ left: "var(--ringi-splitter-left)",
373
+ width: "13px"
374
+ },
375
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("ringi-split-splitter__bar", "h-full transition-[width,background-color,opacity] duration-100", "[transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", isDragging ? "w-[3px] bg-accent-primary/60" : "w-px bg-border-default group-hover/splitter:w-[2px] group-hover/splitter:bg-accent-primary/30") }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
376
+ className: cn("ringi-split-splitter__grip", "pointer-events-none absolute", "flex flex-col items-center gap-[3px]", "transition-opacity duration-100", "[transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", isDragging ? "opacity-100" : "opacity-0 group-hover/splitter:opacity-70 group-focus-visible/splitter:opacity-70"),
377
+ children: [
378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[3px] w-[3px] rounded-full bg-accent-primary/70" }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[3px] w-[3px] rounded-full bg-accent-primary/70" }),
380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[3px] w-[3px] rounded-full bg-accent-primary/70" })
381
+ ]
382
+ })]
383
+ })]
384
+ });
385
+ };
386
+ var pierreDiffOptions = {
387
+ diffIndicators: "bars",
388
+ diffStyle: "split",
389
+ expandUnchanged: true,
390
+ hunkSeparators: "line-info",
391
+ lineDiffType: "word",
392
+ overflow: "scroll",
393
+ theme: "pierre-dark",
394
+ themeType: "dark"
395
+ };
396
+ var MIN_TEXTAREA_ROWS$1 = 2;
397
+ var MAX_TEXTAREA_ROWS$1 = 8;
398
+ var resizeTextarea$1 = (textarea) => {
399
+ if (!textarea) return;
400
+ const styles = window.getComputedStyle(textarea);
401
+ const lineHeight = Number.parseFloat(styles.lineHeight || "0") || 16;
402
+ const paddingTop = Number.parseFloat(styles.paddingTop || "0");
403
+ const paddingBottom = Number.parseFloat(styles.paddingBottom || "0");
404
+ const borderTop = Number.parseFloat(styles.borderTopWidth || "0");
405
+ const borderBottom = Number.parseFloat(styles.borderBottomWidth || "0");
406
+ const minHeight = lineHeight * MIN_TEXTAREA_ROWS$1 + paddingTop + paddingBottom + borderTop + borderBottom;
407
+ const maxHeight = lineHeight * MAX_TEXTAREA_ROWS$1 + paddingTop + paddingBottom + borderTop + borderBottom;
408
+ textarea.style.height = "0px";
409
+ const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
410
+ textarea.style.height = `${nextHeight}px`;
411
+ textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
412
+ };
413
+ var editorPaneClassName = "min-h-[220px] rounded-lg border border-border-default bg-surface-primary shadow-inner shadow-black/10";
414
+ var codeBlockClassName = "h-full min-h-[180px] w-full rounded-md border border-transparent bg-transparent px-3 py-2.5 font-mono text-xs leading-5 text-text-primary outline-none";
415
+ var getOriginalCodeValue = (originalCode) => {
416
+ if (originalCode && originalCode.length > 0) return originalCode;
417
+ return "No original code available";
418
+ };
419
+ var CodeSuggestionEditor = ({ value, onChange, originalCode, filePath, lineNumber }) => {
420
+ const dialogRef = (0, import_react.useRef)(null);
421
+ const inlineTextareaRef = (0, import_react.useRef)(null);
422
+ const expandedTextareaRef = (0, import_react.useRef)(null);
423
+ const [expandedValue, setExpandedValue] = (0, import_react.useState)(value);
424
+ const [isExpanded, setIsExpanded] = (0, import_react.useState)(false);
425
+ (0, import_react.useEffect)(() => {
426
+ resizeTextarea$1(inlineTextareaRef.current);
427
+ }, [value]);
428
+ (0, import_react.useEffect)(() => {
429
+ if (!isExpanded) setExpandedValue(value);
430
+ }, [isExpanded, value]);
431
+ (0, import_react.useEffect)(() => {
432
+ if (!isExpanded) return;
433
+ const focusTimer = window.setTimeout(() => {
434
+ expandedTextareaRef.current?.focus();
435
+ const length = expandedTextareaRef.current?.value.length ?? 0;
436
+ expandedTextareaRef.current?.setSelectionRange(length, length);
437
+ resizeTextarea$1(expandedTextareaRef.current);
438
+ }, 0);
439
+ return () => window.clearTimeout(focusTimer);
440
+ }, [isExpanded]);
441
+ const handleDialogClose = (0, import_react.useCallback)(() => {
442
+ setIsExpanded(false);
443
+ }, []);
444
+ const closeExpandedModal = (0, import_react.useCallback)(() => {
445
+ if (dialogRef.current?.open) dialogRef.current.close();
446
+ setExpandedValue(value);
447
+ setIsExpanded(false);
448
+ }, [value]);
449
+ const openExpandedModal = (0, import_react.useCallback)(() => {
450
+ if (dialogRef.current?.open) return;
451
+ setExpandedValue(value);
452
+ dialogRef.current?.showModal();
453
+ setIsExpanded(true);
454
+ }, [value]);
455
+ const handleApply = (0, import_react.useCallback)(() => {
456
+ onChange(expandedValue);
457
+ if (dialogRef.current?.open) dialogRef.current.close();
458
+ setIsExpanded(false);
459
+ }, [expandedValue, onChange]);
460
+ const handleRemove = (0, import_react.useCallback)(() => {
461
+ onChange("");
462
+ if (dialogRef.current?.open) dialogRef.current.close();
463
+ setExpandedValue("");
464
+ setIsExpanded(false);
465
+ }, [onChange]);
466
+ const handleDialogCancel = (0, import_react.useCallback)((event) => {
467
+ event.preventDefault();
468
+ event.stopPropagation();
469
+ closeExpandedModal();
470
+ }, [closeExpandedModal]);
471
+ const handleExpandedKeyDown = (0, import_react.useCallback)((event) => {
472
+ if (event.key !== "Escape") return;
473
+ event.preventDefault();
474
+ event.stopPropagation();
475
+ closeExpandedModal();
476
+ }, [closeExpandedModal]);
477
+ const handleInlineChange = (0, import_react.useCallback)((event) => {
478
+ onChange(event.target.value);
479
+ resizeTextarea$1(event.target);
480
+ }, [onChange]);
481
+ const handleExpandedChange = (0, import_react.useCallback)((event) => {
482
+ setExpandedValue(event.target.value);
483
+ resizeTextarea$1(event.target);
484
+ }, []);
485
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
486
+ className: "animate-in fade-in rounded-lg border border-border-default/80 bg-surface-elevated duration-100",
487
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
488
+ className: "flex items-center justify-between gap-3 border-b border-border-subtle px-3 py-2",
489
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
490
+ className: "text-[10px] font-medium uppercase tracking-[0.16em] text-text-tertiary",
491
+ children: "Suggestion"
492
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
493
+ className: "flex items-center gap-1",
494
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
495
+ type: "button",
496
+ onClick: openExpandedModal,
497
+ className: "inline-flex h-6 items-center rounded-md px-2 font-mono text-[10px] text-text-secondary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
498
+ "aria-label": "Expand code suggestion editor",
499
+ children: "Expand"
500
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
501
+ type: "button",
502
+ onClick: handleRemove,
503
+ className: "inline-flex h-6 items-center rounded-md px-2 font-mono text-[10px] text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-status-error focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
504
+ "aria-label": "Remove code suggestion",
505
+ children: "Remove"
506
+ })]
507
+ })]
508
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
509
+ className: "px-3 py-2.5",
510
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
511
+ ref: inlineTextareaRef,
512
+ value,
513
+ onChange: handleInlineChange,
514
+ rows: MIN_TEXTAREA_ROWS$1,
515
+ spellCheck: false,
516
+ placeholder: "Suggest a replacement…",
517
+ className: "max-h-[13rem] min-h-[3.5rem] w-full resize-none overflow-y-hidden rounded-md border border-border-default/70 bg-surface-primary px-3 py-2 font-mono text-xs leading-5 text-text-primary shadow-inner shadow-black/10 outline-none transition-colors placeholder:text-text-tertiary/70 focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/30",
518
+ "aria-label": "Suggested replacement code"
519
+ })
520
+ })]
521
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dialog", {
522
+ ref: dialogRef,
523
+ onCancel: handleDialogCancel,
524
+ onClose: handleDialogClose,
525
+ className: "m-auto w-full max-w-5xl rounded-2xl border border-border-default/80 bg-surface-elevated p-0 text-text-primary shadow-2xl shadow-black/40 backdrop:bg-black/50",
526
+ "aria-label": "Expanded code suggestion editor",
527
+ children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
529
+ className: "flex items-center justify-between gap-3 border-b border-border-subtle px-4 py-3",
530
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
531
+ className: "flex min-w-0 items-center gap-2",
532
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
533
+ className: "text-sm font-medium text-text-primary",
534
+ children: "Suggestion"
535
+ }), filePath ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
536
+ className: "truncate font-mono text-[10px] text-text-tertiary",
537
+ children: [filePath, lineNumber != null ? `:${lineNumber}` : ""]
538
+ }) : null]
539
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
540
+ type: "button",
541
+ onClick: closeExpandedModal,
542
+ className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
543
+ "aria-label": "Close expanded code suggestion editor",
544
+ children: "×"
545
+ })]
546
+ }),
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
548
+ className: "grid gap-4 px-4 py-4 lg:grid-cols-2",
549
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
550
+ className: editorPaneClassName,
551
+ "aria-label": "Original code",
552
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
553
+ className: "border-b border-status-error/20 bg-status-error/8 px-3 py-2",
554
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
555
+ className: "font-mono text-[10px] font-medium uppercase tracking-[0.16em] text-diff-remove-text",
556
+ children: "Original"
557
+ })
558
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
559
+ className: cn(codeBlockClassName, "overflow-x-auto whitespace-pre-wrap break-words text-text-secondary"),
560
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: getOriginalCodeValue(originalCode) })
561
+ })]
562
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
563
+ className: editorPaneClassName,
564
+ "aria-label": "Suggested code",
565
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
566
+ className: "border-b border-status-success/20 bg-status-success/8 px-3 py-2",
567
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
568
+ className: "font-mono text-[10px] font-medium uppercase tracking-[0.16em] text-diff-add-text",
569
+ children: "Suggested"
570
+ })
571
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
572
+ ref: expandedTextareaRef,
573
+ value: expandedValue,
574
+ onChange: handleExpandedChange,
575
+ onKeyDown: handleExpandedKeyDown,
576
+ rows: 10,
577
+ spellCheck: false,
578
+ placeholder: "Write the suggested replacement…",
579
+ className: cn(codeBlockClassName, "resize-none overflow-y-auto placeholder:text-text-tertiary/70 focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/30"),
580
+ "aria-label": "Suggested replacement code in expanded editor"
581
+ })]
582
+ })]
583
+ }),
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
585
+ className: "flex items-center justify-end gap-2 border-t border-border-subtle px-4 py-3",
586
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
587
+ type: "button",
588
+ onClick: closeExpandedModal,
589
+ className: "rounded-md px-3 py-1.5 text-xs text-text-secondary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
590
+ children: "Cancel"
591
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
592
+ type: "button",
593
+ onClick: handleApply,
594
+ className: "rounded-md bg-accent-primary px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
595
+ children: "Apply"
596
+ })]
597
+ })
598
+ ]
599
+ })] });
600
+ };
601
+ var MIN_TEXTAREA_ROWS = 2;
602
+ var MAX_TEXTAREA_ROWS = 6;
603
+ var resizeTextarea = (textarea) => {
604
+ if (!textarea) return;
605
+ const styles = window.getComputedStyle(textarea);
606
+ const lineHeight = Number.parseFloat(styles.lineHeight || "0") || 16;
607
+ const paddingTop = Number.parseFloat(styles.paddingTop || "0");
608
+ const paddingBottom = Number.parseFloat(styles.paddingBottom || "0");
609
+ const borderTop = Number.parseFloat(styles.borderTopWidth || "0");
610
+ const borderBottom = Number.parseFloat(styles.borderBottomWidth || "0");
611
+ const minHeight = lineHeight * MIN_TEXTAREA_ROWS + paddingTop + paddingBottom + borderTop + borderBottom;
612
+ const maxHeight = lineHeight * MAX_TEXTAREA_ROWS + paddingTop + paddingBottom + borderTop + borderBottom;
613
+ textarea.style.height = "0px";
614
+ const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
615
+ textarea.style.height = `${nextHeight}px`;
616
+ textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
617
+ };
618
+ var InlineCommentComposer = ({ draft, reviewId, originalCode, onCancel, onSubmitted, onLocalSubmit }) => {
619
+ const [value, setValue] = (0, import_react.useState)("");
620
+ const [suggestion, setSuggestion] = (0, import_react.useState)("");
621
+ const [showSuggestion, setShowSuggestion] = (0, import_react.useState)(false);
622
+ const [submitting, setSubmitting] = (0, import_react.useState)(false);
623
+ const [error, setError] = (0, import_react.useState)(null);
624
+ const textareaRef = (0, import_react.useRef)(null);
625
+ const router = useRouter();
626
+ const errorMessageId = (0, import_react.useId)();
627
+ const trimmedValue = value.trim();
628
+ (0, import_react.useEffect)(() => {
629
+ textareaRef.current?.focus();
630
+ }, []);
631
+ (0, import_react.useEffect)(() => {
632
+ resizeTextarea(textareaRef.current);
633
+ }, [value]);
634
+ const canSubmit = trimmedValue.length > 0 && !submitting && Boolean(reviewId || onLocalSubmit);
635
+ const handleSubmit = (0, import_react.useCallback)(() => {
636
+ if (submitting || trimmedValue.length === 0) return;
637
+ const trimmedSuggestion = suggestion.trim();
638
+ const resolvedSuggestion = showSuggestion && trimmedSuggestion ? trimmedSuggestion : null;
639
+ if (!reviewId) {
640
+ onLocalSubmit?.(trimmedValue, resolvedSuggestion);
641
+ onSubmitted();
642
+ return;
643
+ }
644
+ setSubmitting(true);
645
+ setError(null);
646
+ clientRuntime.runFork(gen(function* submitInlineComment() {
647
+ const { http } = yield* ApiClient;
648
+ return yield* http.comments.create({
649
+ path: { reviewId },
650
+ payload: {
651
+ content: trimmedValue,
652
+ filePath: draft.filePath,
653
+ lineNumber: draft.lineNumber,
654
+ lineType: draft.lineType,
655
+ suggestion: resolvedSuggestion
656
+ }
657
+ });
658
+ }).pipe(tap(() => sync(onSubmitted)), tap(() => promise(() => router.invalidate())), tapCause((cause) => sync(() => setError(pretty(cause)))), ensuring(sync(() => setSubmitting(false)))));
659
+ }, [
660
+ draft.filePath,
661
+ draft.lineNumber,
662
+ draft.lineType,
663
+ onLocalSubmit,
664
+ onSubmitted,
665
+ reviewId,
666
+ router,
667
+ showSuggestion,
668
+ submitting,
669
+ suggestion,
670
+ trimmedValue
671
+ ]);
672
+ const handleFormSubmit = (0, import_react.useCallback)((event) => {
673
+ event.preventDefault();
674
+ handleSubmit();
675
+ }, [handleSubmit]);
676
+ const handleKeyDownCapture = (0, import_react.useCallback)((event) => {
677
+ if (event.key === "Escape") {
678
+ event.preventDefault();
679
+ event.stopPropagation();
680
+ onCancel();
681
+ return;
682
+ }
683
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
684
+ event.preventDefault();
685
+ handleSubmit();
686
+ }
687
+ }, [handleSubmit, onCancel]);
688
+ const handleCommentChange = (0, import_react.useCallback)((event) => {
689
+ setValue(event.target.value);
690
+ resizeTextarea(event.target);
691
+ }, []);
692
+ const handleSuggestionToggle = (0, import_react.useCallback)(() => {
693
+ setShowSuggestion((current) => !current);
694
+ }, []);
695
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
696
+ onSubmit: handleFormSubmit,
697
+ onKeyDownCapture: handleKeyDownCapture,
698
+ className: "animate-in fade-in duration-100 border-t border-border-subtle bg-surface-elevated px-3 py-2.5",
699
+ children: [
700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
701
+ ref: textareaRef,
702
+ value,
703
+ onChange: handleCommentChange,
704
+ rows: MIN_TEXTAREA_ROWS,
705
+ placeholder: "Add a comment... (⌘Enter to submit)",
706
+ className: "w-full resize-none rounded-sm border border-border-default bg-surface-primary p-2 font-mono text-xs text-text-primary transition-[border-color,box-shadow] duration-150 ease placeholder:text-text-tertiary/60 focus:border-accent-primary focus:outline-none focus:ring-1 focus:ring-accent-primary/30",
707
+ "aria-label": `Comment for ${draft.filePath} line ${draft.lineNumber}`,
708
+ "aria-describedby": error ? errorMessageId : void 0,
709
+ "aria-invalid": error ? true : void 0
710
+ }),
711
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
712
+ id: errorMessageId,
713
+ className: "mt-1 text-xs text-status-error",
714
+ role: "alert",
715
+ children: error
716
+ }) : null,
717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
718
+ type: "button",
719
+ onClick: handleSuggestionToggle,
720
+ "aria-expanded": showSuggestion,
721
+ className: "mt-2 rounded px-1 py-0.5 text-xs text-text-secondary transition-colors hover:bg-surface-overlay hover:text-accent-primary focus-visible:bg-surface-overlay focus-visible:text-accent-primary focus-visible:outline-none",
722
+ children: showSuggestion ? "− Remove suggestion" : "+ Add suggested code"
723
+ }),
724
+ showSuggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
725
+ className: "mt-2",
726
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeSuggestionEditor, {
727
+ value: suggestion,
728
+ onChange: setSuggestion,
729
+ originalCode,
730
+ filePath: draft.filePath,
731
+ lineNumber: draft.lineNumber
732
+ })
733
+ }) : null,
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
735
+ className: "mt-2 flex items-center justify-end gap-2",
736
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
737
+ type: "button",
738
+ onClick: onCancel,
739
+ className: "rounded px-2 py-1 text-xs text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-text-secondary focus-visible:bg-surface-overlay focus-visible:text-text-secondary focus-visible:outline-none",
740
+ children: "Cancel"
741
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
742
+ type: "submit",
743
+ disabled: !canSubmit,
744
+ className: cn("rounded bg-accent-primary px-2.5 py-1 text-xs font-medium text-white transition-[transform,background-color] duration-150 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] hover:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40 active:scale-[0.97] disabled:cursor-not-allowed disabled:opacity-40 motion-reduce:transform-none", submitting && "pointer-events-none"),
745
+ children: submitting ? "Posting…" : "Comment"
746
+ })]
747
+ })
748
+ ]
749
+ });
750
+ };
751
+ var INITIAL_VISIBLE_COMMENTS = 3;
752
+ var formatCompactDate = (value) => {
753
+ const date = new Date(value);
754
+ if (Number.isNaN(date.getTime())) return value;
755
+ return new Intl.DateTimeFormat(void 0, {
756
+ day: "numeric",
757
+ month: "short"
758
+ }).format(date);
759
+ };
760
+ var getCommentInitials = (commentId) => commentId.slice(0, 2).toUpperCase();
761
+ var CommentCard = ({ comment, index, error, pending, runCommentAction }) => {
762
+ const resolveLabel = comment.resolved ? "Unresolve" : "Resolve";
763
+ const handleResolveToggle = (0, import_react.useCallback)(() => {
764
+ runCommentAction(comment.id, comment.resolved ? "unresolve" : "resolve");
765
+ }, [
766
+ comment.id,
767
+ comment.resolved,
768
+ runCommentAction
769
+ ]);
770
+ const handleDelete = (0, import_react.useCallback)(() => {
771
+ runCommentAction(comment.id, "delete");
772
+ }, [comment.id, runCommentAction]);
773
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", {
774
+ className: cn("group animate-in fade-in duration-100 rounded bg-surface-elevated/50 px-2 py-1.5 transition-opacity", comment.resolved && "opacity-50"),
775
+ style: { animationDelay: `${index * 40}ms` },
776
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
777
+ className: "flex items-start gap-2",
778
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
779
+ className: "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full bg-text-tertiary/10 font-mono text-[9px] font-medium text-text-tertiary",
780
+ children: getCommentInitials(comment.id)
781
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
782
+ className: "min-w-0 flex-1",
783
+ children: [
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
785
+ className: "flex items-center gap-1.5 text-[10px] text-text-tertiary",
786
+ children: [
787
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
788
+ className: "font-mono",
789
+ children: formatCompactDate(comment.createdAt)
790
+ }),
791
+ comment.resolved ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
792
+ className: "text-[10px] text-text-tertiary",
793
+ children: "· Resolved"
794
+ }) : null,
795
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
796
+ className: "ml-auto flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",
797
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
798
+ type: "button",
799
+ onClick: handleResolveToggle,
800
+ disabled: pending,
801
+ "aria-label": `${resolveLabel} comment ${comment.id}`,
802
+ className: "rounded px-1 py-0.5 text-[10px] text-text-secondary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:bg-surface-overlay focus-visible:text-text-primary focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
803
+ children: resolveLabel
804
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
805
+ type: "button",
806
+ onClick: handleDelete,
807
+ disabled: pending,
808
+ "aria-label": `Delete comment ${comment.id}`,
809
+ className: "rounded px-1 py-0.5 text-[10px] text-text-tertiary transition-colors hover:bg-status-error/10 hover:text-status-error focus-visible:bg-status-error/10 focus-visible:text-status-error focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
810
+ children: "Delete"
811
+ })]
812
+ })
813
+ ]
814
+ }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
816
+ className: "mt-1 whitespace-pre-wrap break-words text-xs leading-5 text-text-primary",
817
+ children: comment.content
818
+ }),
819
+ comment.suggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
820
+ className: "mt-1.5 rounded bg-surface-primary/60 px-2 py-1",
821
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
822
+ className: "overflow-x-auto whitespace-pre-wrap break-words font-mono text-[10px] leading-4 text-text-secondary",
823
+ children: comment.suggestion
824
+ })
825
+ }) : null,
826
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
827
+ className: "mt-1 text-[10px] text-status-error",
828
+ children: error
829
+ }) : null
830
+ ]
831
+ })]
832
+ })
833
+ });
834
+ };
835
+ var InlineCommentThread = ({ comments, reviewId }) => {
836
+ const [expanded, setExpanded] = (0, import_react.useState)(false);
837
+ const [pendingCommentId, setPendingCommentId] = (0, import_react.useState)(null);
838
+ const [errorByCommentId, setErrorByCommentId] = (0, import_react.useState)({});
839
+ const router = useRouter();
840
+ const hiddenCount = Math.max(0, comments.length - INITIAL_VISIBLE_COMMENTS);
841
+ const visibleComments = expanded ? comments : comments.slice(0, INITIAL_VISIBLE_COMMENTS);
842
+ const runCommentAction = (0, import_react.useCallback)((commentId, action) => {
843
+ if (pendingCommentId) return;
844
+ setPendingCommentId(commentId);
845
+ setErrorByCommentId((current) => ({
846
+ ...current,
847
+ [commentId]: null
848
+ }));
849
+ clientRuntime.runFork(gen(function* performCommentAction() {
850
+ const { http } = yield* ApiClient;
851
+ if (action === "resolve") return yield* http.comments.resolve({ path: { id: commentId } });
852
+ if (action === "unresolve") return yield* http.comments.unresolve({ path: { id: commentId } });
853
+ return yield* http.comments.remove({ path: { id: commentId } });
854
+ }).pipe(tap(() => promise(() => router.invalidate())), tapCause((cause) => sync(() => {
855
+ setErrorByCommentId((current) => ({
856
+ ...current,
857
+ [commentId]: pretty(cause)
858
+ }));
859
+ })), ensuring(sync(() => setPendingCommentId(null)))));
860
+ }, [pendingCommentId, router]);
861
+ const toggleExpanded = (0, import_react.useCallback)(() => {
862
+ setExpanded((current) => !current);
863
+ }, []);
864
+ if (comments.length === 0) return null;
865
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
866
+ "data-review-id": reviewId,
867
+ className: "animate-in fade-in duration-100 pl-2",
868
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
869
+ className: "flex flex-col gap-1.5",
870
+ children: [visibleComments.map((comment, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommentCard, {
871
+ comment,
872
+ index,
873
+ error: errorByCommentId[comment.id] ?? null,
874
+ pending: pendingCommentId === comment.id,
875
+ runCommentAction
876
+ }, comment.id)), hiddenCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
877
+ type: "button",
878
+ onClick: toggleExpanded,
879
+ "aria-expanded": expanded,
880
+ className: "self-start rounded px-1 py-0.5 text-[10px] font-medium text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-text-secondary focus-visible:bg-surface-overlay focus-visible:text-text-secondary focus-visible:outline-none",
881
+ children: expanded ? "Show less" : `Show ${hiddenCount} more`
882
+ }) : null]
883
+ })
884
+ });
885
+ };
886
+ /**
887
+ * Reconstruct a unified diff patch string from ringi's DiffFile schema.
888
+ *
889
+ * @pierre/diffs has a well-tested patch parser that produces correct
890
+ * FileDiffMetadata (with all line indices, hunk counts, etc.) from a
891
+ * patch string. Building FileDiffMetadata by hand is fragile because
892
+ * the internal renderer relies on exact index alignment between
893
+ * deletionLines/additionLines arrays and hunkContent groups.
894
+ *
895
+ * By reconstructing the patch text and using PatchDiff, we delegate
896
+ * all that bookkeeping to the library's own parser.
897
+ */
898
+ function toPatchString(file) {
899
+ const oldPath = file.oldPath || file.newPath;
900
+ const { newPath } = file;
901
+ return [`--- a/${oldPath}\n+++ b/${newPath}`, ...file.hunks.map(hunkToPatch)].join("\n");
902
+ }
903
+ function hunkToPatch(hunk) {
904
+ return [`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`, ...hunk.lines.map(lineToPatch)].join("\n");
905
+ }
906
+ var LINE_PREFIX = {
907
+ added: "+",
908
+ context: " ",
909
+ removed: "-"
910
+ };
911
+ function lineToPatch(line) {
912
+ return `${LINE_PREFIX[line.type]}${line.content}`;
913
+ }
914
+ var EMPTY_COMMENTS$1 = [];
915
+ var statusBadge = {
916
+ added: {
917
+ className: "bg-status-success/15 text-status-success",
918
+ label: "A"
919
+ },
920
+ deleted: {
921
+ className: "bg-status-error/15 text-status-error",
922
+ label: "D"
923
+ },
924
+ modified: {
925
+ className: "bg-status-warning/15 text-status-warning",
926
+ label: "M"
927
+ },
928
+ renamed: {
929
+ className: "bg-status-info/15 text-status-info",
930
+ label: "R"
931
+ }
932
+ };
933
+ var buildActiveCommentKey = (lineNumber, side) => `${lineNumber}:${side}`;
934
+ var sideToCommentLineType = (side) => side === "additions" ? "added" : "removed";
935
+ var diffFileReducer = (state, action) => {
936
+ switch (action.type) {
937
+ case "toggle_expand": return {
938
+ ...state,
939
+ expanded: !state.expanded
940
+ };
941
+ case "fetch_start": return {
942
+ ...state,
943
+ error: null,
944
+ loading: true
945
+ };
946
+ case "set_hunks": return {
947
+ ...state,
948
+ hunks: action.hunks,
949
+ loading: false
950
+ };
951
+ case "fetch_error": return {
952
+ ...state,
953
+ error: action.message,
954
+ loading: false
955
+ };
956
+ case "fetch_done": return {
957
+ ...state,
958
+ loading: false
959
+ };
960
+ case "toggle_comment_line": return {
961
+ ...state,
962
+ activeComment: state.activeComment?.key === action.draft.key ? null : action.draft
963
+ };
964
+ case "close_comment": return {
965
+ ...state,
966
+ activeComment: null
967
+ };
968
+ case "add_local_comment": return {
969
+ ...state,
970
+ activeComment: null,
971
+ localComments: [...state.localComments, action.comment]
972
+ };
973
+ case "delete_local_comment": return {
974
+ ...state,
975
+ localComments: state.localComments.filter((c) => c.id !== action.id)
976
+ };
977
+ default: return state;
978
+ }
979
+ };
980
+ var fetchHunks = (reviewId, filePath) => gen(function* loadReviewHunks() {
981
+ const { http } = yield* ApiClient;
982
+ const { hunks } = yield* http.reviewFiles.hunks({
983
+ path: { reviewId },
984
+ urlParams: { path: filePath }
985
+ });
986
+ return hunks;
987
+ }).pipe(timeout("30 seconds"), withSpan("fetchHunks", { attributes: {
988
+ filePath,
989
+ reviewId
990
+ } }));
991
+ var useLazyHunks = (reviewId, filePath, expanded, hasHunks, dispatch) => {
992
+ const fetchedRef = (0, import_react.useRef)(false);
993
+ (0, import_react.useEffect)(() => {
994
+ if (!expanded || !reviewId || hasHunks) return;
995
+ if (fetchedRef.current) return;
996
+ fetchedRef.current = true;
997
+ dispatch({ type: "fetch_start" });
998
+ const fiber = clientRuntime.runFork(fetchHunks(reviewId, filePath).pipe(matchCauseEffect({
999
+ onFailure: (cause) => hasInterruptsOnly(cause) ? sync(() => {
1000
+ fetchedRef.current = false;
1001
+ dispatch({ type: "fetch_done" });
1002
+ }) : sync(() => dispatch({
1003
+ message: pretty(cause),
1004
+ type: "fetch_error"
1005
+ })),
1006
+ onSuccess: (hunks) => sync(() => dispatch({
1007
+ hunks,
1008
+ type: "set_hunks"
1009
+ }))
1010
+ })));
1011
+ return () => {
1012
+ clientRuntime.runFork(interrupt(fiber));
1013
+ };
1014
+ }, [
1015
+ dispatch,
1016
+ expanded,
1017
+ filePath,
1018
+ hasHunks,
1019
+ reviewId
1020
+ ]);
1021
+ };
1022
+ /**
1023
+ * The "+" button shown in the gutter when hovering a commentable diff line.
1024
+ *
1025
+ * Rendering context (from @pierre/diffs):
1026
+ * The library's `renderGutterUtility` slot mechanism works as follows:
1027
+ * Shadow DOM: [data-column-number] (position: relative)
1028
+ * └─ [data-gutter-utility-slot] (position: absolute; right:0; top:0; bottom:0;
1029
+ * │ display: flex; justify-content: flex-end)
1030
+ * │ └─ <slot name="gutter-utility-slot">
1031
+ * Light DOM: <div slot="gutter-utility-slot" ← projected into shadow slot
1032
+ * style="position: absolute; top:0; bottom:0; text-align: center">
1033
+ * └─ THIS BUTTON
1034
+ *
1035
+ * The slotted wrapper div has `position: absolute` (set by the library's
1036
+ * GutterUtilitySlotStyles), which takes it out of the flex flow of the
1037
+ * shadow-DOM [data-gutter-utility-slot] container. This collapses the
1038
+ * container to zero width. Without corrective styles the button ends up
1039
+ * overflowing into the code-content column — effectively invisible.
1040
+ *
1041
+ * We fix this by adding a CSS override (in styles.css) that changes the
1042
+ * slotted wrapper to `position: static !important` so it participates in
1043
+ * the parent's flex layout. Then the button sits inside the gutter column,
1044
+ * aligned to the flex-end, matching the library's own default utility
1045
+ * button placement.
1046
+ *
1047
+ * The button itself mirrors the library's `[data-utility-button]` styles:
1048
+ * • `position: relative; z-index: 4` to paint above line-number content.
1049
+ */
1050
+ var GutterAddButton = ({ getHoveredLine, onAddComment }) => {
1051
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1052
+ type: "button",
1053
+ onClick: (0, import_react.useCallback)(() => {
1054
+ const hoveredLine = getHoveredLine();
1055
+ if (!hoveredLine) return;
1056
+ onAddComment(hoveredLine.lineNumber, hoveredLine.side);
1057
+ }, [getHoveredLine, onAddComment]),
1058
+ className: "ringi-gutter-add-btn relative z-[4] flex items-center justify-center rounded-sm bg-accent-primary text-[11px] font-medium text-white shadow-sm shadow-accent-primary/30 transition-[transform,background-color] duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] hover:bg-accent-primary-hover focus-visible:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40 active:scale-[0.92]",
1059
+ "aria-label": "Add comment on this line",
1060
+ tabIndex: 0,
1061
+ children: "+"
1062
+ });
1063
+ };
1064
+ var formatCompactTimestamp = (iso) => {
1065
+ const date = new Date(iso);
1066
+ if (Number.isNaN(date.getTime())) return "";
1067
+ return new Intl.DateTimeFormat(void 0, {
1068
+ hour: "numeric",
1069
+ minute: "2-digit"
1070
+ }).format(date);
1071
+ };
1072
+ var LocalCommentCard = ({ comment, index, onDelete }) => {
1073
+ const handleDelete = (0, import_react.useCallback)(() => {
1074
+ onDelete(comment.id);
1075
+ }, [comment.id, onDelete]);
1076
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", {
1077
+ className: "ringi-comment-card group rounded bg-surface-elevated/50 px-2 py-1.5",
1078
+ style: { animationDelay: `${index * 40}ms` },
1079
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1080
+ className: "flex items-start gap-2",
1081
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1082
+ className: "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full bg-text-tertiary/10 text-[10px] text-text-tertiary",
1083
+ children: "✎"
1084
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1085
+ className: "min-w-0 flex-1",
1086
+ children: [
1087
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1088
+ className: "flex items-center gap-1.5 text-[10px] text-text-tertiary",
1089
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1090
+ className: "font-mono",
1091
+ children: formatCompactTimestamp(comment.createdAt)
1092
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1093
+ type: "button",
1094
+ onClick: handleDelete,
1095
+ "aria-label": "Delete comment",
1096
+ className: "ml-auto rounded px-1 py-0.5 text-[10px] text-text-tertiary opacity-0 transition-opacity hover:bg-status-error/10 hover:text-status-error focus-visible:bg-status-error/10 focus-visible:text-status-error focus-visible:opacity-100 focus-visible:outline-none group-hover:opacity-100",
1097
+ children: "Delete"
1098
+ })]
1099
+ }),
1100
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1101
+ className: "mt-1 whitespace-pre-wrap break-words text-xs leading-5 text-text-primary",
1102
+ children: comment.content
1103
+ }),
1104
+ comment.suggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1105
+ className: "mt-1.5 rounded bg-surface-primary/60 px-2 py-1",
1106
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
1107
+ className: "overflow-x-auto whitespace-pre-wrap break-words font-mono text-[10px] leading-4 text-text-secondary",
1108
+ children: comment.suggestion
1109
+ })
1110
+ }) : null
1111
+ ]
1112
+ })]
1113
+ })
1114
+ });
1115
+ };
1116
+ var DiffFile = ({ file, defaultExpanded = false, reviewId, diffMode = "unified", comments = EMPTY_COMMENTS$1, onLocalCommentsChange, viewed = false, onToggleViewed, pendingDeleteId, onPendingDeleteHandled }) => {
1117
+ const [state, dispatch] = (0, import_react.useReducer)(diffFileReducer, {
1118
+ activeComment: null,
1119
+ error: null,
1120
+ expanded: defaultExpanded,
1121
+ hunks: file.hunks,
1122
+ loading: false,
1123
+ localComments: []
1124
+ });
1125
+ const { expanded, hunks, loading, error, activeComment, localComments } = state;
1126
+ const badge = statusBadge[file.status];
1127
+ useLazyHunks(reviewId, file.newPath, expanded, hunks.length > 0, dispatch);
1128
+ (0, import_react.useEffect)(() => {
1129
+ onLocalCommentsChange?.(file.newPath, localComments);
1130
+ }, [
1131
+ file.newPath,
1132
+ localComments,
1133
+ onLocalCommentsChange
1134
+ ]);
1135
+ (0, import_react.useEffect)(() => {
1136
+ if (!pendingDeleteId) return;
1137
+ dispatch({
1138
+ id: pendingDeleteId,
1139
+ type: "delete_local_comment"
1140
+ });
1141
+ onPendingDeleteHandled?.();
1142
+ }, [pendingDeleteId, onPendingDeleteHandled]);
1143
+ const handleAddComment = (0, import_react.useCallback)((lineNumber, side) => {
1144
+ dispatch({
1145
+ draft: {
1146
+ filePath: file.newPath,
1147
+ key: buildActiveCommentKey(lineNumber, side),
1148
+ lineNumber,
1149
+ lineType: sideToCommentLineType(side),
1150
+ side
1151
+ },
1152
+ type: "toggle_comment_line"
1153
+ });
1154
+ }, [file.newPath]);
1155
+ const handleCloseComment = (0, import_react.useCallback)(() => {
1156
+ dispatch({ type: "close_comment" });
1157
+ }, []);
1158
+ const handleLocalSubmit = (0, import_react.useCallback)((content, suggestion) => {
1159
+ if (!activeComment) return;
1160
+ dispatch({
1161
+ comment: {
1162
+ content,
1163
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1164
+ filePath: activeComment.filePath,
1165
+ id: `local-${crypto.randomUUID()}`,
1166
+ lineNumber: activeComment.lineNumber,
1167
+ lineType: activeComment.lineType,
1168
+ side: activeComment.side,
1169
+ suggestion
1170
+ },
1171
+ type: "add_local_comment"
1172
+ });
1173
+ }, [activeComment]);
1174
+ const handleDeleteLocalComment = (0, import_react.useCallback)((id) => {
1175
+ dispatch({
1176
+ id,
1177
+ type: "delete_local_comment"
1178
+ });
1179
+ }, []);
1180
+ const handleToggleExpand = (0, import_react.useCallback)(() => {
1181
+ dispatch({ type: "toggle_expand" });
1182
+ }, []);
1183
+ const handleToggleViewed = (0, import_react.useCallback)(() => {
1184
+ onToggleViewed?.(file.newPath);
1185
+ }, [file.newPath, onToggleViewed]);
1186
+ const getOriginalCodeForLine = (0, import_react.useCallback)((lineNumber, side) => {
1187
+ for (const hunk of hunks) for (const line of hunk.lines) if ((side === "additions" ? line.newLineNumber : line.oldLineNumber) === lineNumber) return line.content;
1188
+ return "";
1189
+ }, [hunks]);
1190
+ const getOrCreateAnnotation = (map, key) => {
1191
+ const existing = map.get(key);
1192
+ if (existing) return existing;
1193
+ const fresh = {
1194
+ comments: [],
1195
+ draft: null,
1196
+ localComments: []
1197
+ };
1198
+ map.set(key, fresh);
1199
+ return fresh;
1200
+ };
1201
+ const lineAnnotations = (0, import_react.useMemo)(() => {
1202
+ const annotationMap = /* @__PURE__ */ new Map();
1203
+ for (const comment of comments) {
1204
+ if (comment.lineNumber === null || comment.lineNumber === void 0) continue;
1205
+ const side = comment.lineType === "removed" ? "deletions" : "additions";
1206
+ const entry = getOrCreateAnnotation(annotationMap, buildActiveCommentKey(comment.lineNumber, side));
1207
+ entry.comments = [...entry.comments, comment];
1208
+ }
1209
+ for (const local of localComments) {
1210
+ const entry = getOrCreateAnnotation(annotationMap, buildActiveCommentKey(local.lineNumber, local.side));
1211
+ entry.localComments = [...entry.localComments, local];
1212
+ }
1213
+ if (activeComment) {
1214
+ const entry = getOrCreateAnnotation(annotationMap, activeComment.key);
1215
+ entry.draft = activeComment;
1216
+ }
1217
+ return [...annotationMap.entries()].map(([key, metadata]) => {
1218
+ const [lineNumber, side] = key.split(":");
1219
+ return {
1220
+ lineNumber: Number(lineNumber),
1221
+ metadata,
1222
+ side
1223
+ };
1224
+ });
1225
+ }, [
1226
+ activeComment,
1227
+ comments,
1228
+ localComments
1229
+ ]);
1230
+ const patchString = (0, import_react.useMemo)(() => {
1231
+ if (hunks.length === 0) return "";
1232
+ return toPatchString({
1233
+ ...file,
1234
+ hunks
1235
+ });
1236
+ }, [file, hunks]);
1237
+ const diffOptions = {
1238
+ ...pierreDiffOptions,
1239
+ diffStyle: diffMode,
1240
+ disableFileHeader: true,
1241
+ enableGutterUtility: true,
1242
+ lineHoverHighlight: "both"
1243
+ };
1244
+ const renderAnnotation = (0, import_react.useCallback)((annotation) => {
1245
+ if (!annotation.metadata) return null;
1246
+ const { draft, comments: lineComments, localComments: lineLocalComments } = annotation.metadata;
1247
+ if (!(draft || lineComments.length > 0 || lineLocalComments.length > 0)) return null;
1248
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1249
+ lineComments.length > 0 && reviewId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineCommentThread, {
1250
+ comments: lineComments,
1251
+ reviewId
1252
+ }) : null,
1253
+ lineLocalComments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1254
+ className: "pl-2",
1255
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1256
+ className: "flex flex-col gap-1.5",
1257
+ children: lineLocalComments.map((lc, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocalCommentCard, {
1258
+ comment: lc,
1259
+ index: i,
1260
+ onDelete: handleDeleteLocalComment
1261
+ }, lc.id))
1262
+ })
1263
+ }) : null,
1264
+ draft ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineCommentComposer, {
1265
+ draft,
1266
+ reviewId,
1267
+ originalCode: getOriginalCodeForLine(draft.lineNumber, draft.side),
1268
+ onCancel: handleCloseComment,
1269
+ onSubmitted: handleCloseComment,
1270
+ onLocalSubmit: reviewId ? void 0 : handleLocalSubmit
1271
+ }) : null
1272
+ ] });
1273
+ }, [
1274
+ getOriginalCodeForLine,
1275
+ handleCloseComment,
1276
+ handleDeleteLocalComment,
1277
+ handleLocalSubmit,
1278
+ reviewId
1279
+ ]);
1280
+ const renderGutterUtility = (0, import_react.useCallback)((getHoveredLine) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GutterAddButton, {
1281
+ getHoveredLine,
1282
+ onAddComment: handleAddComment
1283
+ }), [handleAddComment]);
1284
+ let diffContent;
1285
+ if (loading) diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1286
+ className: "px-4 py-3 text-xs italic text-text-tertiary",
1287
+ children: "Loading diff…"
1288
+ });
1289
+ else if (error) diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1290
+ className: "px-4 py-3 text-xs italic text-status-error",
1291
+ children: error
1292
+ });
1293
+ else if (hunks.length === 0) diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1294
+ className: "px-4 py-3 text-xs italic text-text-tertiary",
1295
+ children: "Binary file or mode change"
1296
+ });
1297
+ else diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SplitDiffSplitter, {
1298
+ enabled: diffMode === "split",
1299
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PatchDiff, {
1300
+ patch: patchString,
1301
+ options: diffOptions,
1302
+ lineAnnotations,
1303
+ renderAnnotation,
1304
+ renderGutterUtility,
1305
+ className: "ringi-diff-file"
1306
+ })
1307
+ });
1308
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1309
+ id: `diff-file-${file.newPath.replaceAll("/", "-")}`,
1310
+ className: "overflow-hidden rounded-sm border border-border-subtle bg-surface-elevated",
1311
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1312
+ className: "flex items-center gap-1.5 px-2.5 py-1.5",
1313
+ children: [
1314
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1315
+ type: "button",
1316
+ onClick: handleToggleExpand,
1317
+ className: "inline-flex size-4 shrink-0 items-center justify-center rounded text-text-tertiary/60 transition-[color,background-color] duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] hover:text-text-secondary focus-visible:text-text-secondary focus-visible:outline-none",
1318
+ "aria-expanded": expanded,
1319
+ "aria-label": expanded ? "Collapse file" : "Expand file",
1320
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
1321
+ size: 12,
1322
+ className: cn("transition-transform duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none", expanded && "rotate-90")
1323
+ })
1324
+ }),
1325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1326
+ className: cn("w-3 shrink-0 text-center font-mono text-[10px] font-semibold leading-none", badge.className.includes("success") ? "text-diff-add-text" : badge.className.includes("error") ? "text-diff-remove-text" : badge.className.includes("warning") ? "text-accent-primary" : "text-status-info"),
1327
+ children: badge.label
1328
+ }),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1330
+ className: "min-w-0 flex-1 truncate font-mono text-[11px] text-text-primary",
1331
+ children: file.newPath
1332
+ }),
1333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1334
+ className: "flex shrink-0 items-center gap-1.5 text-[10px] tabular-nums text-text-tertiary/60",
1335
+ children: [file.additions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1336
+ className: "text-diff-add-text/60",
1337
+ children: ["+", file.additions]
1338
+ }) : null, file.deletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1339
+ className: "text-diff-remove-text/60",
1340
+ children: ["-", file.deletions]
1341
+ }) : null]
1342
+ }),
1343
+ onToggleViewed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1344
+ type: "button",
1345
+ onClick: handleToggleViewed,
1346
+ title: viewed ? "Unmark as viewed" : "Mark as viewed",
1347
+ className: cn("ml-0.5 flex size-5 shrink-0 items-center justify-center rounded transition-[transform,background-color,color] duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] active:scale-[0.93] motion-reduce:transform-none", viewed ? "bg-status-success/12 text-status-success" : "text-text-tertiary/40 hover:bg-surface-overlay hover:text-text-secondary"),
1348
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
1349
+ width: "11",
1350
+ height: "11",
1351
+ viewBox: "0 0 12 12",
1352
+ fill: "none",
1353
+ className: "shrink-0",
1354
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
1355
+ d: "M2.5 6.5L5 9l4.5-6",
1356
+ stroke: "currentColor",
1357
+ strokeWidth: viewed ? "1.75" : "1.25",
1358
+ strokeLinecap: "round",
1359
+ strokeLinejoin: "round"
1360
+ })
1361
+ })
1362
+ }) : null
1363
+ ]
1364
+ }), expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1365
+ className: "border-t border-border-subtle",
1366
+ children: diffContent
1367
+ }) : null]
1368
+ });
1369
+ };
1370
+ var EMPTY_COMMENTS = [];
1371
+ /**
1372
+ * Single-file diff renderer.
1373
+ *
1374
+ * Renders **only** the currently selected file. The previous implementation
1375
+ * mounted every DiffFile in the review, which meant N × PatchDiff instances
1376
+ * on load. This version renders exactly one.
1377
+ */
1378
+ var DiffView = ({ file, reviewId, diffMode = "split", comments = EMPTY_COMMENTS, onLocalCommentsChange, viewed = false, onToggleViewed, pendingDeleteId, onPendingDeleteHandled }) => {
1379
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffFile, {
1380
+ file,
1381
+ comments: (0, import_react.useMemo)(() => comments.filter((c) => c.filePath === file.newPath), [comments, file.newPath]),
1382
+ defaultExpanded: true,
1383
+ diffMode,
1384
+ reviewId,
1385
+ onLocalCommentsChange,
1386
+ viewed,
1387
+ onToggleViewed,
1388
+ pendingDeleteId,
1389
+ onPendingDeleteHandled
1390
+ }, file.newPath);
1391
+ };
1392
+ var EMPTY_IDS = [];
1393
+ var TreeContext = (0, import_react.createContext)(void 0);
1394
+ var useTree = () => {
1395
+ const ctx = (0, import_react.useContext)(TreeContext);
1396
+ if (!ctx) throw new Error("Tree components must be used within <Tree>");
1397
+ return ctx;
1398
+ };
1399
+ var NodeContext = (0, import_react.createContext)(void 0);
1400
+ var useNode = () => {
1401
+ const ctx = (0, import_react.useContext)(NodeContext);
1402
+ if (!ctx) throw new Error("TreeNode components must be used within <TreeNode>");
1403
+ return ctx;
1404
+ };
1405
+ var Tree = ({ children, defaultExpandedIds = EMPTY_IDS, expandedIds: controlledExpanded, onExpandedChange, selectedIds: controlledSelected, onSelectionChange, showIcons = true, indent = 14, className }) => {
1406
+ const [internalExpanded, setInternalExpanded] = (0, import_react.useState)(() => new Set(defaultExpandedIds));
1407
+ const isExpandedControlled = controlledExpanded !== void 0 && onExpandedChange !== void 0;
1408
+ const expandedIds = isExpandedControlled ? controlledExpanded : internalExpanded;
1409
+ const toggleExpanded = (0, import_react.useCallback)((id) => {
1410
+ const next = new Set(expandedIds);
1411
+ if (next.has(id)) next.delete(id);
1412
+ else next.add(id);
1413
+ if (isExpandedControlled && onExpandedChange) onExpandedChange(next);
1414
+ else setInternalExpanded(next);
1415
+ }, [
1416
+ expandedIds,
1417
+ isExpandedControlled,
1418
+ onExpandedChange
1419
+ ]);
1420
+ const [internalSelected, setInternalSelected] = (0, import_react.useState)([]);
1421
+ const isSelControlled = controlledSelected !== void 0 && onSelectionChange !== void 0;
1422
+ const selectedIds = isSelControlled ? controlledSelected : internalSelected;
1423
+ const handleSelection = (0, import_react.useCallback)((id, _ctrlKey) => {
1424
+ const next = selectedIds.includes(id) ? [] : [id];
1425
+ if (isSelControlled && onSelectionChange) onSelectionChange(next);
1426
+ else setInternalSelected(next);
1427
+ }, [
1428
+ selectedIds,
1429
+ isSelControlled,
1430
+ onSelectionChange
1431
+ ]);
1432
+ const ctx = (0, import_react.useMemo)(() => ({
1433
+ expandedIds,
1434
+ handleSelection,
1435
+ indent,
1436
+ selectedIds,
1437
+ showIcons,
1438
+ toggleExpanded
1439
+ }), [
1440
+ expandedIds,
1441
+ selectedIds,
1442
+ toggleExpanded,
1443
+ handleSelection,
1444
+ showIcons,
1445
+ indent
1446
+ ]);
1447
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeContext.Provider, {
1448
+ value: ctx,
1449
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1450
+ className: cn("w-full", className),
1451
+ role: "tree",
1452
+ children
1453
+ })
1454
+ });
1455
+ };
1456
+ var TreeNode = ({ nodeId: providedId, level = 0, children, className, ...props }) => {
1457
+ const generatedId = (0, import_react.useId)();
1458
+ const nodeId = providedId ?? generatedId;
1459
+ const ctx = (0, import_react.useMemo)(() => ({
1460
+ level,
1461
+ nodeId
1462
+ }), [nodeId, level]);
1463
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NodeContext.Provider, {
1464
+ value: ctx,
1465
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1466
+ className: cn("select-none", className),
1467
+ role: "treeitem",
1468
+ ...props,
1469
+ children
1470
+ })
1471
+ });
1472
+ };
1473
+ var TreeNodeTrigger = ({ children, className, onClick, ...props }) => {
1474
+ const { selectedIds, toggleExpanded, handleSelection, indent } = useTree();
1475
+ const { nodeId, level } = useNode();
1476
+ const isSelected = selectedIds.includes(nodeId);
1477
+ const handleClick = (0, import_react.useCallback)((e) => {
1478
+ toggleExpanded(nodeId);
1479
+ handleSelection(nodeId, e.ctrlKey || e.metaKey);
1480
+ onClick?.(e);
1481
+ }, [
1482
+ toggleExpanded,
1483
+ handleSelection,
1484
+ nodeId,
1485
+ onClick
1486
+ ]);
1487
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1488
+ type: "button",
1489
+ "aria-current": isSelected ? "true" : void 0,
1490
+ "data-selected": isSelected ? "" : void 0,
1491
+ className: cn("group relative flex w-full cursor-pointer items-center text-left outline-none", "transition-colors duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", "focus-visible:ring-1 focus-visible:ring-accent-primary/40", className),
1492
+ onClick: handleClick,
1493
+ style: { paddingLeft: level * indent + 8 },
1494
+ ...props,
1495
+ children
1496
+ });
1497
+ };
1498
+ var TreeNodeContent = ({ children, className, forceMount, ...props }) => {
1499
+ const { expandedIds } = useTree();
1500
+ const { nodeId } = useNode();
1501
+ const isExpanded = expandedIds.has(nodeId);
1502
+ if (!isExpanded && !forceMount) return null;
1503
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1504
+ className: cn(!isExpanded && "hidden", className),
1505
+ role: "group",
1506
+ ...props,
1507
+ children
1508
+ });
1509
+ };
1510
+ var TreeExpander = ({ hasChildren = false, className, ...props }) => {
1511
+ const { expandedIds } = useTree();
1512
+ const { nodeId } = useNode();
1513
+ const isExpanded = expandedIds.has(nodeId);
1514
+ if (!hasChildren) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1515
+ className: "inline-flex h-4 w-4 shrink-0",
1516
+ "aria-hidden": true
1517
+ });
1518
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1519
+ className: cn("inline-flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground", "transition-transform duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", isExpanded && "rotate-90", className),
1520
+ "aria-hidden": true,
1521
+ ...props,
1522
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "h-3 w-3" })
1523
+ });
1524
+ };
1525
+ var TreeIcon = ({ icon, hasChildren = false, className, ...props }) => {
1526
+ const { showIcons, expandedIds } = useTree();
1527
+ const { nodeId } = useNode();
1528
+ const isExpanded = expandedIds.has(nodeId);
1529
+ if (!showIcons) return null;
1530
+ const getDefaultIcon = () => {
1531
+ if (!hasChildren) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(File, { className: "h-3.5 w-3.5" });
1532
+ if (isExpanded) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderOpen, { className: "h-3.5 w-3.5" });
1533
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Folder, { className: "h-3.5 w-3.5" });
1534
+ };
1535
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1536
+ className: cn("mr-1.5 inline-flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground/70", className),
1537
+ "aria-hidden": true,
1538
+ ...props,
1539
+ children: icon ?? getDefaultIcon()
1540
+ });
1541
+ };
1542
+ var EASE_OUT = "[transition-timing-function:cubic-bezier(0.23,1,0.32,1)]";
1543
+ var collapseTree = (node) => {
1544
+ for (const child of node.children) collapseTree(child);
1545
+ while (node.children.length === 1 && !node.children[0].file && node.children[0].children.length > 0) {
1546
+ const [onlyChild] = node.children;
1547
+ node.name = node.name ? `${node.name}/${onlyChild.name}` : onlyChild.name;
1548
+ node.path = onlyChild.path;
1549
+ node.children = onlyChild.children;
1550
+ }
1551
+ };
1552
+ var sortTree = (node) => {
1553
+ node.children.sort((a, b) => {
1554
+ const aIsDir = !a.file;
1555
+ if (aIsDir !== !b.file) return aIsDir ? -1 : 1;
1556
+ return a.name.localeCompare(b.name);
1557
+ });
1558
+ for (const child of node.children) sortTree(child);
1559
+ };
1560
+ var collectDirPaths = (node) => {
1561
+ const paths = [];
1562
+ for (const child of node.children) if (!child.file) {
1563
+ paths.push(child.path);
1564
+ paths.push(...collectDirPaths(child));
1565
+ }
1566
+ return paths;
1567
+ };
1568
+ var flatFileList = (node) => {
1569
+ const result = [];
1570
+ for (const child of node.children) if (child.file) result.push(child.file.newPath);
1571
+ else result.push(...flatFileList(child));
1572
+ return result;
1573
+ };
1574
+ var buildTree = (files) => {
1575
+ const root = {
1576
+ children: [],
1577
+ name: "",
1578
+ path: ""
1579
+ };
1580
+ for (const file of files) {
1581
+ const segments = file.newPath.split("/");
1582
+ let current = root;
1583
+ for (const [index, segment] of segments.entries()) {
1584
+ const isFile = index === segments.length - 1;
1585
+ const childPath = segments.slice(0, index + 1).join("/");
1586
+ let child = current.children.find((c) => c.name === segment);
1587
+ if (!child) {
1588
+ child = {
1589
+ children: [],
1590
+ name: segment,
1591
+ path: childPath
1592
+ };
1593
+ if (isFile) child.file = file;
1594
+ current.children.push(child);
1595
+ }
1596
+ current = child;
1597
+ }
1598
+ }
1599
+ collapseTree(root);
1600
+ sortTree(root);
1601
+ return root;
1602
+ };
1603
+ var statusColor = {
1604
+ added: "text-diff-add-text",
1605
+ deleted: "text-diff-remove-text",
1606
+ modified: "text-accent-primary",
1607
+ renamed: "text-status-info"
1608
+ };
1609
+ var statusLetter = {
1610
+ added: "A",
1611
+ deleted: "D",
1612
+ modified: "M",
1613
+ renamed: "R"
1614
+ };
1615
+ var ViewedCheckbox = ({ checked, onToggle, filePath }) => {
1616
+ const handleClick = (0, import_react.useCallback)((event) => {
1617
+ event.stopPropagation();
1618
+ onToggle?.(filePath);
1619
+ }, [filePath, onToggle]);
1620
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
1621
+ className: cn("relative flex size-3 shrink-0 cursor-pointer items-center justify-center rounded-[3px] border transition-[border-color,background-color,color] duration-100", EASE_OUT, checked ? "border-status-success/50 bg-status-success/15 text-status-success" : "border-border-subtle text-transparent hover:border-text-tertiary/40"),
1622
+ onClick: handleClick,
1623
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
1624
+ type: "checkbox",
1625
+ checked,
1626
+ readOnly: true,
1627
+ className: "sr-only",
1628
+ "aria-label": checked ? "Unmark as viewed" : "Mark as viewed",
1629
+ tabIndex: 0
1630
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
1631
+ width: "7",
1632
+ height: "7",
1633
+ viewBox: "0 0 7 7",
1634
+ fill: "none",
1635
+ className: cn("pointer-events-none transition-opacity duration-100", checked ? "opacity-100" : "opacity-0"),
1636
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
1637
+ d: "M1 3.5L2.75 5.25L6 1.75",
1638
+ stroke: "currentColor",
1639
+ strokeWidth: "1.25",
1640
+ strokeLinecap: "round",
1641
+ strokeLinejoin: "round"
1642
+ })
1643
+ })]
1644
+ });
1645
+ };
1646
+ var FileTreeContext = (0, import_react.createContext)(null);
1647
+ var useFileTreeContext = () => {
1648
+ const ctx = (0, import_react.useContext)(FileTreeContext);
1649
+ if (!ctx) throw new Error("FileItem must be rendered inside <FileTree>");
1650
+ return ctx;
1651
+ };
1652
+ var FileItem = ({ node, depth }) => {
1653
+ const { selectedFile, onSelectFile, onToggleViewed, onStageFile, reviewedFiles } = useFileTreeContext();
1654
+ const { file } = node;
1655
+ const filePath = file?.newPath ?? "";
1656
+ const isSelected = selectedFile === filePath;
1657
+ const isReviewed = reviewedFiles?.has(filePath) ?? false;
1658
+ const handleSelect = (0, import_react.useCallback)(() => {
1659
+ if (filePath) onSelectFile(filePath);
1660
+ }, [onSelectFile, filePath]);
1661
+ const handleStage = (0, import_react.useCallback)((e) => {
1662
+ e.stopPropagation();
1663
+ onStageFile?.(filePath);
1664
+ }, [onStageFile, filePath]);
1665
+ const handleKeyDown = (0, import_react.useCallback)((e) => {
1666
+ if (e.key === "Enter" || e.key === " ") {
1667
+ e.preventDefault();
1668
+ handleSelect();
1669
+ }
1670
+ }, [handleSelect]);
1671
+ if (!file) return null;
1672
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeNode, {
1673
+ nodeId: filePath,
1674
+ level: depth,
1675
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1676
+ role: "row",
1677
+ "aria-current": isSelected ? "true" : void 0,
1678
+ "data-selected": isSelected ? "" : void 0,
1679
+ onClick: handleSelect,
1680
+ onKeyDown: handleKeyDown,
1681
+ tabIndex: 0,
1682
+ className: cn("group flex w-full cursor-pointer items-center gap-1.5 py-[3px] pr-2 text-left outline-none", "transition-[background-color,border-color,opacity] duration-100", EASE_OUT, isSelected ? "border-l-2 border-accent-primary bg-accent-muted/40" : "border-l-2 border-transparent hover:bg-surface-overlay/40", isReviewed && !isSelected && "opacity-45", "focus-visible:ring-1 focus-visible:ring-accent-primary/40"),
1683
+ style: { paddingLeft: `${depth * 14 + 8}px` },
1684
+ children: [
1685
+ onToggleViewed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewedCheckbox, {
1686
+ checked: isReviewed,
1687
+ onToggle: onToggleViewed,
1688
+ filePath
1689
+ }) : null,
1690
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1691
+ className: cn("inline-flex w-3 shrink-0 text-center font-mono text-[10px] font-semibold leading-none", statusColor[file.status]),
1692
+ children: statusLetter[file.status]
1693
+ }),
1694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1695
+ className: cn("min-w-0 flex-1 truncate font-mono text-[11px]", isSelected ? "text-text-primary" : "text-text-secondary"),
1696
+ children: node.name
1697
+ }),
1698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1699
+ className: cn("ml-auto flex shrink-0 items-center gap-1 text-[10px] tabular-nums", "transition-opacity duration-100", isSelected ? "opacity-50" : "opacity-0 group-hover:opacity-50"),
1700
+ children: [
1701
+ onStageFile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1702
+ type: "button",
1703
+ onClick: handleStage,
1704
+ title: `git add ${filePath}`,
1705
+ "aria-label": `Stage ${node.name}`,
1706
+ className: cn("inline-flex h-4 items-center rounded px-1 font-sans text-[9px] font-medium uppercase tracking-wide text-text-tertiary", "transition-[color,background-color] duration-75", "opacity-0 group-hover:opacity-100", "hover:!bg-diff-add-bg hover:!text-diff-add-text", "active:scale-[0.95]", EASE_OUT),
1707
+ children: "+stage"
1708
+ }) : null,
1709
+ file.additions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1710
+ className: "text-diff-add-text/70",
1711
+ children: ["+", file.additions]
1712
+ }) : null,
1713
+ file.deletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1714
+ className: "text-diff-remove-text/70",
1715
+ children: ["-", file.deletions]
1716
+ }) : null
1717
+ ]
1718
+ })
1719
+ ]
1720
+ })
1721
+ });
1722
+ };
1723
+ var DirItem = ({ node, depth }) => {
1724
+ const hasChildren = node.children.length > 0;
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TreeNode, {
1726
+ nodeId: node.path,
1727
+ level: depth,
1728
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TreeNodeTrigger, {
1729
+ className: cn("gap-1 py-[3px] pr-2", "hover:bg-surface-overlay/40"),
1730
+ children: [
1731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeExpander, { hasChildren }),
1732
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeIcon, { hasChildren: true }),
1733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1734
+ className: "truncate text-[11px] text-text-tertiary",
1735
+ children: node.name
1736
+ })
1737
+ ]
1738
+ }), hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeNodeContent, { children: node.children.map((child) => child.file ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileItem, {
1739
+ node: child,
1740
+ depth: depth + 1
1741
+ }, child.path) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirItem, {
1742
+ node: child,
1743
+ depth: depth + 1
1744
+ }, child.path)) }) : null]
1745
+ });
1746
+ };
1747
+ var useFileKeyboardNav = (flatFiles, selectedFile, onSelectFile) => {
1748
+ (0, import_react.useEffect)(() => {
1749
+ const handler = (event) => {
1750
+ const tag = event.target.tagName;
1751
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1752
+ if (event.key === "j" || event.key === "ArrowDown") {
1753
+ event.preventDefault();
1754
+ if (flatFiles.length === 0) return;
1755
+ if (selectedFile === null) {
1756
+ onSelectFile(flatFiles[0]);
1757
+ return;
1758
+ }
1759
+ const idx = flatFiles.indexOf(selectedFile);
1760
+ if (idx < flatFiles.length - 1) onSelectFile(flatFiles[idx + 1]);
1761
+ return;
1762
+ }
1763
+ if (event.key === "k" || event.key === "ArrowUp") {
1764
+ event.preventDefault();
1765
+ if (flatFiles.length === 0) return;
1766
+ if (selectedFile === null) {
1767
+ const last = flatFiles.at(-1);
1768
+ if (last) onSelectFile(last);
1769
+ return;
1770
+ }
1771
+ const idx = flatFiles.indexOf(selectedFile);
1772
+ if (idx > 0) onSelectFile(flatFiles[idx - 1]);
1773
+ }
1774
+ };
1775
+ document.addEventListener("keydown", handler);
1776
+ return () => document.removeEventListener("keydown", handler);
1777
+ }, [
1778
+ flatFiles,
1779
+ onSelectFile,
1780
+ selectedFile
1781
+ ]);
1782
+ };
1783
+ var renderTreeChildren = (nodes, depth) => nodes.map((child) => child.file ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileItem, {
1784
+ node: child,
1785
+ depth
1786
+ }, child.path) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirItem, {
1787
+ node: child,
1788
+ depth
1789
+ }, child.path));
1790
+ var FileTree = ({ files, selectedFile, onSelectFile, reviewedFiles, onToggleViewed, onStageFile, groupLabel, headerAction, emptyStateMessage = "No files" }) => {
1791
+ const [hideViewed, setHideViewed] = (0, import_react.useState)(false);
1792
+ const toggleHideViewed = (0, import_react.useCallback)(() => setHideViewed((p) => !p), []);
1793
+ const reviewedCount = reviewedFiles?.size ?? 0;
1794
+ const visibleFiles = (0, import_react.useMemo)(() => {
1795
+ if (!hideViewed || !reviewedFiles || reviewedFiles.size === 0) return files;
1796
+ return files.filter((f) => !reviewedFiles.has(f.newPath));
1797
+ }, [
1798
+ files,
1799
+ hideViewed,
1800
+ reviewedFiles
1801
+ ]);
1802
+ const hiddenCount = files.length - visibleFiles.length;
1803
+ const tree = (0, import_react.useMemo)(() => buildTree(visibleFiles), [visibleFiles]);
1804
+ const [expanded, setExpanded] = (0, import_react.useState)(() => new Set(collectDirPaths(tree)));
1805
+ const [groupOpen, setGroupOpen] = (0, import_react.useState)(true);
1806
+ (0, import_react.useEffect)(() => {
1807
+ setExpanded(new Set(collectDirPaths(tree)));
1808
+ }, [tree]);
1809
+ const handleExpandedChange = (0, import_react.useCallback)((ids) => {
1810
+ setExpanded(ids);
1811
+ }, []);
1812
+ const toggleGroupOpen = (0, import_react.useCallback)(() => setGroupOpen((p) => !p), []);
1813
+ useFileKeyboardNav((0, import_react.useMemo)(() => flatFileList(tree), [tree]), selectedFile, onSelectFile);
1814
+ const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
1815
+ const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
1816
+ const reviewCtx = (0, import_react.useMemo)(() => ({
1817
+ onSelectFile,
1818
+ onStageFile,
1819
+ onToggleViewed,
1820
+ reviewedFiles,
1821
+ selectedFile
1822
+ }), [
1823
+ selectedFile,
1824
+ onSelectFile,
1825
+ reviewedFiles,
1826
+ onToggleViewed,
1827
+ onStageFile
1828
+ ]);
1829
+ const allReviewed = files.length > 0 && reviewedCount === files.length;
1830
+ const progressPct = files.length > 0 ? reviewedCount / files.length * 100 : 0;
1831
+ const treeContent = groupLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
1832
+ type: "button",
1833
+ onClick: toggleGroupOpen,
1834
+ className: "ringi-tree-item flex w-full items-center gap-1 px-3 py-1.5 text-left",
1835
+ children: [
1836
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1837
+ className: cn("inline-flex size-4 shrink-0 items-center justify-center text-[10px] text-text-tertiary/60 transition-transform duration-100", EASE_OUT, groupOpen && "rotate-90"),
1838
+ children: "▶"
1839
+ }),
1840
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1841
+ className: "truncate text-[11px] font-medium text-text-secondary",
1842
+ children: groupLabel
1843
+ }),
1844
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1845
+ className: "ml-auto text-[10px] tabular-nums text-text-tertiary",
1846
+ children: visibleFiles.length
1847
+ })
1848
+ ]
1849
+ }), groupOpen ? renderTreeChildren(tree.children, 0) : null] }) : renderTreeChildren(tree.children, 0);
1850
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
1851
+ className: "flex h-full w-60 shrink-0 flex-col border-r border-border-default bg-surface-secondary",
1852
+ children: [
1853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1854
+ className: "flex flex-col gap-1.5 px-3 py-2",
1855
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1856
+ className: "flex min-w-0 items-center gap-2",
1857
+ children: headerAction
1858
+ }), files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1859
+ className: "flex items-center gap-2",
1860
+ children: [
1861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1862
+ className: "h-[2px] flex-1 overflow-hidden rounded-full bg-border-subtle",
1863
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1864
+ className: cn("h-full rounded-full transition-[width] duration-300", EASE_OUT, allReviewed ? "bg-status-success/60" : "bg-accent-primary/40"),
1865
+ style: { width: `${progressPct}%` }
1866
+ })
1867
+ }),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1869
+ className: "shrink-0 text-[10px] tabular-nums text-text-tertiary",
1870
+ children: [
1871
+ reviewedCount,
1872
+ "/",
1873
+ files.length
1874
+ ]
1875
+ }),
1876
+ reviewedCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1877
+ type: "button",
1878
+ onClick: toggleHideViewed,
1879
+ "aria-pressed": hideViewed,
1880
+ title: hideViewed ? `Show all (${hiddenCount} hidden)` : "Hide reviewed",
1881
+ className: cn("flex size-5 shrink-0 items-center justify-center rounded transition-[background-color,color] duration-100", EASE_OUT, hideViewed ? "bg-accent-muted text-accent-primary" : "text-text-tertiary/50 hover:bg-surface-overlay hover:text-text-tertiary"),
1882
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
1883
+ width: "10",
1884
+ height: "10",
1885
+ viewBox: "0 0 12 12",
1886
+ fill: "none",
1887
+ className: "shrink-0",
1888
+ children: hideViewed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
1889
+ d: "M1.5 1.5L10.5 10.5",
1890
+ stroke: "currentColor",
1891
+ strokeWidth: "1.25",
1892
+ strokeLinecap: "round"
1893
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
1894
+ d: "M2.4 4.8C1.8 5.4 1.5 6 1.5 6s1.5 3 4.5 3c.6 0 1.1-.1 1.6-.3M9.6 7.2C10.2 6.6 10.5 6 10.5 6s-1.5-3-4.5-3c-.6 0-1.1.1-1.6.3",
1895
+ stroke: "currentColor",
1896
+ strokeWidth: "1.25",
1897
+ strokeLinecap: "round",
1898
+ strokeLinejoin: "round"
1899
+ })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
1900
+ d: "M1.5 6s1.5-3 4.5-3 4.5 3 4.5 3-1.5 3-4.5 3S1.5 6 1.5 6z",
1901
+ stroke: "currentColor",
1902
+ strokeWidth: "1.25",
1903
+ strokeLinecap: "round",
1904
+ strokeLinejoin: "round"
1905
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
1906
+ cx: "6",
1907
+ cy: "6",
1908
+ r: "1.5",
1909
+ stroke: "currentColor",
1910
+ strokeWidth: "1.25"
1911
+ })] })
1912
+ })
1913
+ }) : null
1914
+ ]
1915
+ }) : null]
1916
+ }),
1917
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px bg-border-subtle" }),
1918
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1919
+ className: "flex-1 overflow-x-hidden overflow-y-auto py-0.5",
1920
+ children: visibleFiles.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1921
+ className: "flex h-32 items-center justify-center px-3 text-center text-[11px] text-text-tertiary",
1922
+ children: hideViewed && hiddenCount > 0 ? "All files reviewed" : emptyStateMessage
1923
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileTreeContext.Provider, {
1924
+ value: reviewCtx,
1925
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tree, {
1926
+ expandedIds: expanded,
1927
+ onExpandedChange: handleExpandedChange,
1928
+ showIcons: true,
1929
+ indent: 14,
1930
+ children: treeContent
1931
+ })
1932
+ })
1933
+ }),
1934
+ totalAdditions > 0 || totalDeletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1935
+ className: "flex items-center border-t border-border-subtle px-3 py-1.5",
1936
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1937
+ className: "flex gap-1.5 text-[10px] tabular-nums",
1938
+ children: [totalAdditions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1939
+ className: "text-diff-add-text/60",
1940
+ children: ["+", totalAdditions]
1941
+ }) : null, totalDeletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1942
+ className: "text-diff-remove-text/60",
1943
+ children: ["-", totalDeletions]
1944
+ }) : null]
1945
+ })
1946
+ }) : null
1947
+ ]
1948
+ });
1949
+ };
1950
+ //#endregion
1951
+ export { formatReviewFeedback as a, FileTree as i, DiffView as n, ExportFeedbackModal as r, DiffSummary as t };