jj-plus 0.10.10

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 (404) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +102 -0
  3. package/assets/jj-plus-icon.png +0 -0
  4. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  5. package/dist/bin.cjs +112 -0
  6. package/dist/bin.cjs.map +1 -0
  7. package/dist/cli/options.cjs +376 -0
  8. package/dist/cli/options.cjs.map +1 -0
  9. package/dist/cli/timeline-json.cjs +29 -0
  10. package/dist/cli/timeline-json.cjs.map +1 -0
  11. package/dist/extension/blame-codelens.cjs +228 -0
  12. package/dist/extension/blame-codelens.cjs.map +1 -0
  13. package/dist/extension/command-runner.cjs +99 -0
  14. package/dist/extension/command-runner.cjs.map +1 -0
  15. package/dist/extension/constants.cjs +56 -0
  16. package/dist/extension/constants.cjs.map +1 -0
  17. package/dist/extension/editor-extra-features.cjs +464 -0
  18. package/dist/extension/editor-extra-features.cjs.map +1 -0
  19. package/dist/extension/history-adapters.cjs +702 -0
  20. package/dist/extension/history-adapters.cjs.map +1 -0
  21. package/dist/extension/index.cjs +657 -0
  22. package/dist/extension/index.cjs.map +1 -0
  23. package/dist/extension/repo-timeline-panel.cjs +402 -0
  24. package/dist/extension/repo-timeline-panel.cjs.map +1 -0
  25. package/dist/extension/resolve-file-path.cjs +16 -0
  26. package/dist/extension/resolve-file-path.cjs.map +1 -0
  27. package/dist/extension/revision-diff-nav.cjs +169 -0
  28. package/dist/extension/revision-diff-nav.cjs.map +1 -0
  29. package/dist/extension/settings.cjs +41 -0
  30. package/dist/extension/settings.cjs.map +1 -0
  31. package/dist/extension/timeline-at-line.cjs +72 -0
  32. package/dist/extension/timeline-at-line.cjs.map +1 -0
  33. package/dist/extension/timeline-panel.cjs +1113 -0
  34. package/dist/extension/timeline-panel.cjs.map +1 -0
  35. package/dist/extension/timeline-service.cjs +1154 -0
  36. package/dist/extension/timeline-service.cjs.map +1 -0
  37. package/dist/extension/uri-utils.cjs +75 -0
  38. package/dist/extension/uri-utils.cjs.map +1 -0
  39. package/dist/package.cjs +11 -0
  40. package/dist/package.cjs.map +1 -0
  41. package/dist/shared/blame.cjs +211 -0
  42. package/dist/shared/blame.cjs.map +1 -0
  43. package/dist/shared/diff-content-search.cjs +23 -0
  44. package/dist/shared/diff-content-search.cjs.map +1 -0
  45. package/dist/shared/diff-helpers.cjs +12 -0
  46. package/dist/shared/diff-helpers.cjs.map +1 -0
  47. package/dist/shared/diff-stats.cjs +129 -0
  48. package/dist/shared/diff-stats.cjs.map +1 -0
  49. package/dist/shared/file-oplog.cjs +82 -0
  50. package/dist/shared/file-oplog.cjs.map +1 -0
  51. package/dist/shared/history-helpers.cjs +257 -0
  52. package/dist/shared/history-helpers.cjs.map +1 -0
  53. package/dist/shared/history-search.cjs +85 -0
  54. package/dist/shared/history-search.cjs.map +1 -0
  55. package/dist/shared/jj-revset-suggestions.cjs +41 -0
  56. package/dist/shared/jj-revset-suggestions.cjs.map +1 -0
  57. package/dist/shared/line-history.cjs +181 -0
  58. package/dist/shared/line-history.cjs.map +1 -0
  59. package/dist/shared/list-file-revisions.cjs +36 -0
  60. package/dist/shared/list-file-revisions.cjs.map +1 -0
  61. package/dist/shared/non-textual-details.cjs +43 -0
  62. package/dist/shared/non-textual-details.cjs.map +1 -0
  63. package/dist/shared/resolve-file-path.cjs +14 -0
  64. package/dist/shared/resolve-file-path.cjs.map +1 -0
  65. package/dist/shared/revision-diff-availability.cjs +18 -0
  66. package/dist/shared/revision-diff-availability.cjs.map +1 -0
  67. package/dist/shared/revision-diff-pair.cjs +42 -0
  68. package/dist/shared/revision-diff-pair.cjs.map +1 -0
  69. package/dist/shared/revset.cjs +16 -0
  70. package/dist/shared/revset.cjs.map +1 -0
  71. package/dist/shared/timeline-model.cjs +29 -0
  72. package/dist/shared/timeline-model.cjs.map +1 -0
  73. package/dist/standalone/server.cjs +1733 -0
  74. package/dist/standalone/server.cjs.map +1 -0
  75. package/dist/webview/timeline-template.cjs +214 -0
  76. package/dist/webview/timeline-template.cjs.map +1 -0
  77. package/package.json +320 -0
  78. package/webview-dist/assets/worker-DuV13XDT.js +161 -0
  79. package/webview-dist/chunks/abap-CLvhMVsD.js +1 -0
  80. package/webview-dist/chunks/actionscript-3-Cs6JJXT5.js +1 -0
  81. package/webview-dist/chunks/ada-C5qYipkI.js +1 -0
  82. package/webview-dist/chunks/ahk-CA9gdOdF.js +1 -0
  83. package/webview-dist/chunks/ahk2-_6rzW9BS.js +1 -0
  84. package/webview-dist/chunks/andromeeda-vGVdxbeo.js +1 -0
  85. package/webview-dist/chunks/angular-html-DTpR04n4.js +1 -0
  86. package/webview-dist/chunks/angular-ts-BCtbMvbl.js +1 -0
  87. package/webview-dist/chunks/apache-U0d_L8uA.js +1 -0
  88. package/webview-dist/chunks/apex-KFGFyKwG.js +1 -0
  89. package/webview-dist/chunks/apl-BMZuN1Ql.js +1 -0
  90. package/webview-dist/chunks/applescript-CCn79oCD.js +1 -0
  91. package/webview-dist/chunks/ara-4CJ0cIlV.js +1 -0
  92. package/webview-dist/chunks/asciidoc-SCjQUq34.js +1 -0
  93. package/webview-dist/chunks/asm-Cmm7eHzH.js +1 -0
  94. package/webview-dist/chunks/astro-DrbV4Z7r.js +1 -0
  95. package/webview-dist/chunks/aurora-x-CDeNXAV0.js +1 -0
  96. package/webview-dist/chunks/awk-BWXHIvNe.js +1 -0
  97. package/webview-dist/chunks/ayu-dark-DluEY0Gj.js +1 -0
  98. package/webview-dist/chunks/ayu-light-C3h-C4tm.js +1 -0
  99. package/webview-dist/chunks/ayu-mirage-Bqwy1Gya.js +1 -0
  100. package/webview-dist/chunks/ballerina-B7ZEbQpA.js +1 -0
  101. package/webview-dist/chunks/bat-qdxAdBWQ.js +1 -0
  102. package/webview-dist/chunks/beancount-D-usSTwE.js +1 -0
  103. package/webview-dist/chunks/berry-DKpUyyne.js +1 -0
  104. package/webview-dist/chunks/bibtex-Ci_nEsc7.js +1 -0
  105. package/webview-dist/chunks/bicep-CUHmPFLl.js +1 -0
  106. package/webview-dist/chunks/bird2-CYIUgYqB.js +1 -0
  107. package/webview-dist/chunks/blade-oPV8S6rR.js +1 -0
  108. package/webview-dist/chunks/bsl-_VjlB9mv.js +1 -0
  109. package/webview-dist/chunks/c-DYf-3tob.js +1 -0
  110. package/webview-dist/chunks/c3-D0apG41V.js +1 -0
  111. package/webview-dist/chunks/cadence-CQ2zXKGN.js +1 -0
  112. package/webview-dist/chunks/cairo-DLTphjLi.js +1 -0
  113. package/webview-dist/chunks/catppuccin-frappe-C9Q5zVZY.js +1 -0
  114. package/webview-dist/chunks/catppuccin-latte-CtsyrtPq.js +1 -0
  115. package/webview-dist/chunks/catppuccin-macchiato-R8nAf1vR.js +1 -0
  116. package/webview-dist/chunks/catppuccin-mocha-DjuaAJKi.js +1 -0
  117. package/webview-dist/chunks/chapel-DY-WYB1D.js +1 -0
  118. package/webview-dist/chunks/clarity-CtfBBspg.js +1 -0
  119. package/webview-dist/chunks/clojure-DqKBuwfJ.js +1 -0
  120. package/webview-dist/chunks/cmake-Bj61d0ZC.js +1 -0
  121. package/webview-dist/chunks/cobol-DvdX_6CO.js +1 -0
  122. package/webview-dist/chunks/codeowners-C8r90Shi.js +1 -0
  123. package/webview-dist/chunks/codeql-oeQT6MSM.js +1 -0
  124. package/webview-dist/chunks/coffee-C1kO7jr_.js +1 -0
  125. package/webview-dist/chunks/common-lisp-Cv5bFMCO.js +1 -0
  126. package/webview-dist/chunks/coq-DuBRPxYg.js +1 -0
  127. package/webview-dist/chunks/cpp-CoSarUrI.js +1 -0
  128. package/webview-dist/chunks/crystal-SLJeZ5EP.js +1 -0
  129. package/webview-dist/chunks/csharp-oqKa8noW.js +1 -0
  130. package/webview-dist/chunks/css-cfScyrSP.js +1 -0
  131. package/webview-dist/chunks/csv-Dx-8-gkx.js +1 -0
  132. package/webview-dist/chunks/cue-CE9AQfxI.js +1 -0
  133. package/webview-dist/chunks/cypher-ClKdZ_lG.js +1 -0
  134. package/webview-dist/chunks/d-qD-0Kul2.js +1 -0
  135. package/webview-dist/chunks/dark-plus-Cs2F2srj.js +1 -0
  136. package/webview-dist/chunks/dart-CnvKMtbv.js +1 -0
  137. package/webview-dist/chunks/dax-BkyTk9wS.js +1 -0
  138. package/webview-dist/chunks/desktop-Dlh5hvp9.js +1 -0
  139. package/webview-dist/chunks/diff-woXpYk--.js +1 -0
  140. package/webview-dist/chunks/docker-IyjqRm3v.js +1 -0
  141. package/webview-dist/chunks/dotenv-_5a1GRtc.js +1 -0
  142. package/webview-dist/chunks/dracula-BHWKrbxM.js +1 -0
  143. package/webview-dist/chunks/dracula-soft-5eyTD99u.js +1 -0
  144. package/webview-dist/chunks/dream-maker-DW3nJb8Q.js +1 -0
  145. package/webview-dist/chunks/edge-lvBr3t3s.js +1 -0
  146. package/webview-dist/chunks/elixir-BLoPe0KG.js +1 -0
  147. package/webview-dist/chunks/elm-BveBINBn.js +1 -0
  148. package/webview-dist/chunks/emacs-lisp-c_oH4hRZ.js +1 -0
  149. package/webview-dist/chunks/erb-Bmf8z4ph.js +1 -0
  150. package/webview-dist/chunks/erlang-Cphh6RMH.js +1 -0
  151. package/webview-dist/chunks/everforest-dark-sB-x3p7T.js +1 -0
  152. package/webview-dist/chunks/everforest-light-Df2xbC6M.js +1 -0
  153. package/webview-dist/chunks/fennel-DQxkIbk2.js +1 -0
  154. package/webview-dist/chunks/fish-BJitypiv.js +1 -0
  155. package/webview-dist/chunks/fluent-C03EYrpw.js +1 -0
  156. package/webview-dist/chunks/fortran-fixed-form-DEKoE2YW.js +1 -0
  157. package/webview-dist/chunks/fortran-free-form-CYNrtFtB.js +1 -0
  158. package/webview-dist/chunks/fsharp-D13ZGOAj.js +1 -0
  159. package/webview-dist/chunks/gdresource-xMC7jr7x.js +1 -0
  160. package/webview-dist/chunks/gdscript-DYvRD3hg.js +1 -0
  161. package/webview-dist/chunks/gdshader-CBce3t8t.js +1 -0
  162. package/webview-dist/chunks/genie-CV2tkWYe.js +1 -0
  163. package/webview-dist/chunks/gherkin-DExj1W_8.js +1 -0
  164. package/webview-dist/chunks/git-commit-BSykSTBG.js +1 -0
  165. package/webview-dist/chunks/git-rebase-BBii7GBM.js +1 -0
  166. package/webview-dist/chunks/github-dark-C-LZuMrd.js +1 -0
  167. package/webview-dist/chunks/github-dark-default-DXG-b-1a.js +1 -0
  168. package/webview-dist/chunks/github-dark-dimmed-Bx1FflLF.js +1 -0
  169. package/webview-dist/chunks/github-dark-high-contrast-B_tTalzw.js +1 -0
  170. package/webview-dist/chunks/github-light-EUqPIrTm.js +1 -0
  171. package/webview-dist/chunks/github-light-default-BXViO-2h.js +1 -0
  172. package/webview-dist/chunks/github-light-high-contrast-B68TUdTA.js +1 -0
  173. package/webview-dist/chunks/gleam-CSRkHgEL.js +1 -0
  174. package/webview-dist/chunks/glimmer-js-CuCPna-X.js +1 -0
  175. package/webview-dist/chunks/glimmer-ts-DNAjZNDK.js +1 -0
  176. package/webview-dist/chunks/glsl-COQvRxdz.js +1 -0
  177. package/webview-dist/chunks/gn-ilITqXS6.js +1 -0
  178. package/webview-dist/chunks/gnuplot-7GGW24-e.js +1 -0
  179. package/webview-dist/chunks/go-rLFTqkRN.js +1 -0
  180. package/webview-dist/chunks/graphql-1cKCVreZ.js +1 -0
  181. package/webview-dist/chunks/groovy-CacY0gHj.js +1 -0
  182. package/webview-dist/chunks/gruvbox-dark-hard-C820rvS2.js +1 -0
  183. package/webview-dist/chunks/gruvbox-dark-medium-BPjhmG05.js +1 -0
  184. package/webview-dist/chunks/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  185. package/webview-dist/chunks/gruvbox-light-hard-BC_s9l72.js +1 -0
  186. package/webview-dist/chunks/gruvbox-light-medium-BAWPOn9u.js +1 -0
  187. package/webview-dist/chunks/gruvbox-light-soft-BSMLrYjP.js +1 -0
  188. package/webview-dist/chunks/hack-7CXYxWGO.js +1 -0
  189. package/webview-dist/chunks/haml-CeE16iG9.js +1 -0
  190. package/webview-dist/chunks/handlebars-CLm7Miue.js +1 -0
  191. package/webview-dist/chunks/haskell-D8IpX4py.js +1 -0
  192. package/webview-dist/chunks/haxe-C8oCnwJJ.js +1 -0
  193. package/webview-dist/chunks/hcl-Dh228itO.js +1 -0
  194. package/webview-dist/chunks/hjson-CxZEssPk.js +1 -0
  195. package/webview-dist/chunks/hlsl-Cvrh5tZx.js +1 -0
  196. package/webview-dist/chunks/horizon-CE9ld1lL.js +1 -0
  197. package/webview-dist/chunks/horizon-bright-DSNQnXHK.js +1 -0
  198. package/webview-dist/chunks/houston-CsvMBhTu.js +1 -0
  199. package/webview-dist/chunks/html-EbCri-_j.js +1 -0
  200. package/webview-dist/chunks/html-derivative-DGcE2kQi.js +1 -0
  201. package/webview-dist/chunks/http-DpnqgCg2.js +1 -0
  202. package/webview-dist/chunks/hurl-B6NGpamo.js +1 -0
  203. package/webview-dist/chunks/hxml-Dcm88srW.js +1 -0
  204. package/webview-dist/chunks/hy-CZbG8q4J.js +1 -0
  205. package/webview-dist/chunks/imba-DsUTQ-LC.js +1 -0
  206. package/webview-dist/chunks/ini-B5eOa1yu.js +1 -0
  207. package/webview-dist/chunks/java-ZkUoUSJy.js +1 -0
  208. package/webview-dist/chunks/javascript-DLd0EIw7.js +1 -0
  209. package/webview-dist/chunks/jinja-WhIqNp3q.js +1 -0
  210. package/webview-dist/chunks/jison-CIn1t7S8.js +1 -0
  211. package/webview-dist/chunks/json-AHG_JuKN.js +1 -0
  212. package/webview-dist/chunks/json5-BR5RXkoi.js +1 -0
  213. package/webview-dist/chunks/jsonc-CYpm1nAK.js +1 -0
  214. package/webview-dist/chunks/jsonl-CmCQp5Yx.js +1 -0
  215. package/webview-dist/chunks/jsonnet-CJTPZ8u_.js +1 -0
  216. package/webview-dist/chunks/jssm-DXw9l8Rf.js +1 -0
  217. package/webview-dist/chunks/jsx-BLy2KGb2.js +1 -0
  218. package/webview-dist/chunks/julia-XrhdGMY0.js +1 -0
  219. package/webview-dist/chunks/just-CiO9rOBq.js +1 -0
  220. package/webview-dist/chunks/kanagawa-dragon-Bozd7klA.js +1 -0
  221. package/webview-dist/chunks/kanagawa-lotus-BmuOG9ZW.js +1 -0
  222. package/webview-dist/chunks/kanagawa-wave-BML5mF2J.js +1 -0
  223. package/webview-dist/chunks/kdl-CsD5j6eV.js +1 -0
  224. package/webview-dist/chunks/kotlin-DhhofPvG.js +1 -0
  225. package/webview-dist/chunks/kusto-C7mF5XQf.js +1 -0
  226. package/webview-dist/chunks/laserwave-C_8bwKvT.js +1 -0
  227. package/webview-dist/chunks/latex-4tqdYtbu.js +1 -0
  228. package/webview-dist/chunks/lean-CewbzKMR.js +1 -0
  229. package/webview-dist/chunks/less-DVTAwKKz.js +1 -0
  230. package/webview-dist/chunks/light-plus-DVQuIRkW.js +1 -0
  231. package/webview-dist/chunks/liquid-BiiWRCLG.js +1 -0
  232. package/webview-dist/chunks/llvm-DqHOMS-s.js +1 -0
  233. package/webview-dist/chunks/log-BNLmms1o.js +1 -0
  234. package/webview-dist/chunks/logo-Cluzi2Zq.js +1 -0
  235. package/webview-dist/chunks/lua-BPFdS2Mc.js +1 -0
  236. package/webview-dist/chunks/luau-C2pBCSgt.js +1 -0
  237. package/webview-dist/chunks/make-Dixweg8N.js +1 -0
  238. package/webview-dist/chunks/markdown-BYOwaDjH.js +1 -0
  239. package/webview-dist/chunks/marko-BLi1Yy1V.js +1 -0
  240. package/webview-dist/chunks/material-theme-Bm3Qr25_.js +1 -0
  241. package/webview-dist/chunks/material-theme-darker-2IIEA8gg.js +1 -0
  242. package/webview-dist/chunks/material-theme-lighter-uhdI0v04.js +1 -0
  243. package/webview-dist/chunks/material-theme-ocean-CHQ94UKr.js +1 -0
  244. package/webview-dist/chunks/material-theme-palenight-B5W6OYN7.js +1 -0
  245. package/webview-dist/chunks/matlab-D7qyCx1q.js +1 -0
  246. package/webview-dist/chunks/mdc-Dx-qyM1x.js +1 -0
  247. package/webview-dist/chunks/mdx-DQZ5AkYe.js +1 -0
  248. package/webview-dist/chunks/mermaid-BxpfTIzt.js +1 -0
  249. package/webview-dist/chunks/min-dark-BSWPekZh.js +1 -0
  250. package/webview-dist/chunks/min-light-DDpmG2fV.js +1 -0
  251. package/webview-dist/chunks/mipsasm-BMqwQI7S.js +1 -0
  252. package/webview-dist/chunks/mojo-fg40_ntQ.js +1 -0
  253. package/webview-dist/chunks/monokai-B_bV9PQ9.js +1 -0
  254. package/webview-dist/chunks/moonbit-DgjMoLWr.js +1 -0
  255. package/webview-dist/chunks/move-BYnGhZoM.js +1 -0
  256. package/webview-dist/chunks/narrat-_X_XdTYD.js +1 -0
  257. package/webview-dist/chunks/nextflow-BJtWHP5T.js +1 -0
  258. package/webview-dist/chunks/nextflow-groovy-DJMQeKeT.js +1 -0
  259. package/webview-dist/chunks/nginx-rnpjAIVq.js +1 -0
  260. package/webview-dist/chunks/night-owl-DhmEMT88.js +1 -0
  261. package/webview-dist/chunks/night-owl-light-eJ-hLW7d.js +1 -0
  262. package/webview-dist/chunks/nim-kKTJ2fIs.js +1 -0
  263. package/webview-dist/chunks/nix-IvuFDN5E.js +1 -0
  264. package/webview-dist/chunks/nord-Cb4Vim4T.js +1 -0
  265. package/webview-dist/chunks/nsis-CjNLYCLg.js +1 -0
  266. package/webview-dist/chunks/nushell-DHYfO66s.js +1 -0
  267. package/webview-dist/chunks/objective-c-D1A_Heim.js +1 -0
  268. package/webview-dist/chunks/objective-cpp-BsSzOQcm.js +1 -0
  269. package/webview-dist/chunks/ocaml-O90oeIOV.js +1 -0
  270. package/webview-dist/chunks/odin-B1RWQWA5.js +1 -0
  271. package/webview-dist/chunks/one-dark-pro-CLwyXe_n.js +1 -0
  272. package/webview-dist/chunks/one-light-D7Lr4KcI.js +1 -0
  273. package/webview-dist/chunks/openscad-BUDT5pXO.js +1 -0
  274. package/webview-dist/chunks/org-BGWCjkJX.js +1 -0
  275. package/webview-dist/chunks/pascal-4ZHwLPI5.js +1 -0
  276. package/webview-dist/chunks/perl-DEimDILQ.js +1 -0
  277. package/webview-dist/chunks/php-QnaNN--1.js +1 -0
  278. package/webview-dist/chunks/pierre-dark-CpLgRqie.js +1 -0
  279. package/webview-dist/chunks/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  280. package/webview-dist/chunks/pierre-dark-soft-kZQmAZld.js +1 -0
  281. package/webview-dist/chunks/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  282. package/webview-dist/chunks/pierre-dark-vibrant-CpQYzh95.js +1 -0
  283. package/webview-dist/chunks/pierre-light-CoaEpmwp.js +1 -0
  284. package/webview-dist/chunks/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  285. package/webview-dist/chunks/pierre-light-soft-lWLdNTOI.js +1 -0
  286. package/webview-dist/chunks/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  287. package/webview-dist/chunks/pierre-light-vibrant-D80Fkn33.js +1 -0
  288. package/webview-dist/chunks/pkl-ot-7Btpt.js +1 -0
  289. package/webview-dist/chunks/plastic-DQwYfKfQ.js +1 -0
  290. package/webview-dist/chunks/plsql-DGHpHOYJ.js +1 -0
  291. package/webview-dist/chunks/po-BiJDBrnU.js +1 -0
  292. package/webview-dist/chunks/poimandres-DRFjx7u4.js +1 -0
  293. package/webview-dist/chunks/polar-C7UOKdEL.js +1 -0
  294. package/webview-dist/chunks/postcss-BXeXVLqQ.js +1 -0
  295. package/webview-dist/chunks/powerquery-DNMTfnFr.js +1 -0
  296. package/webview-dist/chunks/powershell-CCSX75yM.js +1 -0
  297. package/webview-dist/chunks/prisma-DMgNhnYS.js +1 -0
  298. package/webview-dist/chunks/prolog-iXnhIJG7.js +1 -0
  299. package/webview-dist/chunks/proto-DB4EqR-F.js +1 -0
  300. package/webview-dist/chunks/pug-D5rsKmMX.js +1 -0
  301. package/webview-dist/chunks/puppet-CDv2pdJW.js +1 -0
  302. package/webview-dist/chunks/purescript-9MfHhQsQ.js +1 -0
  303. package/webview-dist/chunks/python-gzcpVVnB.js +1 -0
  304. package/webview-dist/chunks/qml-wvbZb8UE.js +1 -0
  305. package/webview-dist/chunks/qmldir-DCQb3MpD.js +1 -0
  306. package/webview-dist/chunks/qss-Fe1Jh2GI.js +1 -0
  307. package/webview-dist/chunks/r-BNyBFaU1.js +1 -0
  308. package/webview-dist/chunks/racket-DcIDlBhZ.js +1 -0
  309. package/webview-dist/chunks/raku-B3gFvitq.js +1 -0
  310. package/webview-dist/chunks/razor-bKaHS6Kz.js +1 -0
  311. package/webview-dist/chunks/rbs-C0MpOrj4.js +1 -0
  312. package/webview-dist/chunks/red-DX1BQOs8.js +1 -0
  313. package/webview-dist/chunks/reg-CRGYupPL.js +1 -0
  314. package/webview-dist/chunks/regexp-B-mZuWUh.js +1 -0
  315. package/webview-dist/chunks/rel-BtDbiS_P.js +1 -0
  316. package/webview-dist/chunks/riscv-Ckw8ddFX.js +1 -0
  317. package/webview-dist/chunks/ron-VUp2lXgN.js +1 -0
  318. package/webview-dist/chunks/rose-pine-aMsaMD1D.js +1 -0
  319. package/webview-dist/chunks/rose-pine-dawn-8PwUyIlP.js +1 -0
  320. package/webview-dist/chunks/rose-pine-moon-DO-qXg-I.js +1 -0
  321. package/webview-dist/chunks/rosmsg-CAekHB0j.js +1 -0
  322. package/webview-dist/chunks/rst-Qq8ybXHe.js +1 -0
  323. package/webview-dist/chunks/ruby-U659LEwU.js +1 -0
  324. package/webview-dist/chunks/rust-Cfkwpbl8.js +1 -0
  325. package/webview-dist/chunks/sas-C4NtLApe.js +1 -0
  326. package/webview-dist/chunks/sass-DXrisJhu.js +1 -0
  327. package/webview-dist/chunks/scala-BiFt_7mu.js +1 -0
  328. package/webview-dist/chunks/scheme-DQCgrYNe.js +1 -0
  329. package/webview-dist/chunks/scss-KAaA4sHk.js +1 -0
  330. package/webview-dist/chunks/sdbl-bTVj8UrX.js +1 -0
  331. package/webview-dist/chunks/shaderlab-TOUzSsQk.js +1 -0
  332. package/webview-dist/chunks/shellscript-_7bOVsRC.js +1 -0
  333. package/webview-dist/chunks/shellsession-Bs_5k0qk.js +1 -0
  334. package/webview-dist/chunks/slack-dark-DnToyrRv.js +1 -0
  335. package/webview-dist/chunks/slack-ochin-B2OO5cIa.js +1 -0
  336. package/webview-dist/chunks/smalltalk-BHpQb5nM.js +1 -0
  337. package/webview-dist/chunks/smithy-Dgaz7aur.js +1 -0
  338. package/webview-dist/chunks/snazzy-light-4G7pJPwS.js +1 -0
  339. package/webview-dist/chunks/solarized-dark-UdsmMDCL.js +1 -0
  340. package/webview-dist/chunks/solarized-light-C1IUL_tW.js +1 -0
  341. package/webview-dist/chunks/solidity-Bj7E1mRn.js +1 -0
  342. package/webview-dist/chunks/soy-C5iuoFCh.js +1 -0
  343. package/webview-dist/chunks/sparql-D_iOobhT.js +1 -0
  344. package/webview-dist/chunks/splunk-BC2Px7Mm.js +1 -0
  345. package/webview-dist/chunks/sql-JX305rZh.js +1 -0
  346. package/webview-dist/chunks/ssh-config-BgfXC-Er.js +1 -0
  347. package/webview-dist/chunks/stata-2Rtik3TV.js +1 -0
  348. package/webview-dist/chunks/stylus-B6D30XZt.js +1 -0
  349. package/webview-dist/chunks/surrealql-IvpiLAIH.js +1 -0
  350. package/webview-dist/chunks/svelte-DeZcV_1u.js +1 -0
  351. package/webview-dist/chunks/swift-CyEgAFGc.js +1 -0
  352. package/webview-dist/chunks/synthwave-84-nFMaYfgc.js +1 -0
  353. package/webview-dist/chunks/system-verilog-Dh5ihWbf.js +1 -0
  354. package/webview-dist/chunks/systemd-BxMlprV5.js +1 -0
  355. package/webview-dist/chunks/talonscript-CohzipZa.js +1 -0
  356. package/webview-dist/chunks/tasl-DMoTqEGO.js +1 -0
  357. package/webview-dist/chunks/tcl-CZd0xW_V.js +1 -0
  358. package/webview-dist/chunks/templ-CcwjPXJr.js +1 -0
  359. package/webview-dist/chunks/terraform-DswuEJGm.js +1 -0
  360. package/webview-dist/chunks/tex-CZYHgHl8.js +1 -0
  361. package/webview-dist/chunks/tokyo-night-oM2G3aXe.js +1 -0
  362. package/webview-dist/chunks/toml-CcmNWLt0.js +1 -0
  363. package/webview-dist/chunks/ts-tags-DPINfEO_.js +1 -0
  364. package/webview-dist/chunks/tsv-sltzmVWM.js +1 -0
  365. package/webview-dist/chunks/tsx-Dq7pSIyE.js +1 -0
  366. package/webview-dist/chunks/turtle-ByJddavk.js +1 -0
  367. package/webview-dist/chunks/twig-DFpC8yDL.js +1 -0
  368. package/webview-dist/chunks/typescript-CN-guYmK.js +1 -0
  369. package/webview-dist/chunks/typespec-BRdr0IET.js +1 -0
  370. package/webview-dist/chunks/typst-hgW4q6mN.js +1 -0
  371. package/webview-dist/chunks/v-DBj4K9d_.js +1 -0
  372. package/webview-dist/chunks/vala-zf12oZj6.js +1 -0
  373. package/webview-dist/chunks/vb-DOwzaHZ0.js +1 -0
  374. package/webview-dist/chunks/verilog-B2gwz-sJ.js +1 -0
  375. package/webview-dist/chunks/vesper-DdrHHSXu.js +1 -0
  376. package/webview-dist/chunks/vhdl-BroJfC0k.js +1 -0
  377. package/webview-dist/chunks/viml-DvXPmvsu.js +1 -0
  378. package/webview-dist/chunks/vitesse-black-fwtXNY1n.js +1 -0
  379. package/webview-dist/chunks/vitesse-dark-BZCL-v6S.js +1 -0
  380. package/webview-dist/chunks/vitesse-light-VbXTXTou.js +1 -0
  381. package/webview-dist/chunks/vue-Dkz9ntel.js +1 -0
  382. package/webview-dist/chunks/vue-html-Dg8LQ5v3.js +1 -0
  383. package/webview-dist/chunks/vue-vine-CIEXICEN.js +1 -0
  384. package/webview-dist/chunks/vyper-CgoNMtux.js +1 -0
  385. package/webview-dist/chunks/wasm-BnjxR4X6.js +1 -0
  386. package/webview-dist/chunks/wasm-ByWQv1Qj.js +1 -0
  387. package/webview-dist/chunks/wenyan-C8pVoKbM.js +1 -0
  388. package/webview-dist/chunks/wgsl-BsKzXJz4.js +1 -0
  389. package/webview-dist/chunks/wikitext-ClFFjSW2.js +1 -0
  390. package/webview-dist/chunks/wit-DdvCle-K.js +1 -0
  391. package/webview-dist/chunks/wolfram-DLL8P-h_.js +1 -0
  392. package/webview-dist/chunks/worker-D52zl71O.js +1 -0
  393. package/webview-dist/chunks/worker-pool-lOjh8Q2A.js +1543 -0
  394. package/webview-dist/chunks/xml-X7YOen_c.js +1 -0
  395. package/webview-dist/chunks/xsl-h4d_Px-X.js +1 -0
  396. package/webview-dist/chunks/yaml-BRDpsfaj.js +1 -0
  397. package/webview-dist/chunks/zenscript-BnlCZFoB.js +1 -0
  398. package/webview-dist/chunks/zig-CMLA9XwU.js +1 -0
  399. package/webview-dist/index.html +18 -0
  400. package/webview-dist/pierre-worker-portable.js +13633 -0
  401. package/webview-dist/repo-timeline-app.js +7 -0
  402. package/webview-dist/repo-timeline.html +14 -0
  403. package/webview-dist/timeline-app.css +2 -0
  404. package/webview-dist/timeline-app.js +85 -0
@@ -0,0 +1,1733 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_package = require("../package.cjs");
4
+ const require_history_helpers = require("../shared/history-helpers.cjs");
5
+ const require_non_textual_details = require("../shared/non-textual-details.cjs");
6
+ const require_diff_stats = require("../shared/diff-stats.cjs");
7
+ const require_file_oplog = require("../shared/file-oplog.cjs");
8
+ const require_timeline_model = require("../shared/timeline-model.cjs");
9
+ let node_fs = require("node:fs");
10
+ node_fs = require_runtime.__toESM(node_fs, 1);
11
+ let node_path = require("node:path");
12
+ node_path = require_runtime.__toESM(node_path, 1);
13
+ let node_child_process = require("node:child_process");
14
+ let node_fs_promises = require("node:fs/promises");
15
+ node_fs_promises = require_runtime.__toESM(node_fs_promises, 1);
16
+ let node_http = require("node:http");
17
+ node_http = require_runtime.__toESM(node_http, 1);
18
+ let node_os = require("node:os");
19
+ node_os = require_runtime.__toESM(node_os, 1);
20
+ let node_url = require("node:url");
21
+ //#region src/standalone/server.ts
22
+ const execFileAsync = (0, require("node:util").promisify)(node_child_process.execFile);
23
+ const __dirname$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
24
+ const rootDir = node_path.default.resolve(__dirname$1, "..", "..");
25
+ const distDir = node_path.default.join(rootDir, "webview-dist");
26
+ const displayLocale = "en-US";
27
+ const displayTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
28
+ const monthFormatter = new Intl.DateTimeFormat(displayLocale, {
29
+ month: "long",
30
+ timeZone: displayTimeZone
31
+ });
32
+ const shortDateFormatter = new Intl.DateTimeFormat(displayLocale, {
33
+ month: "short",
34
+ day: "numeric",
35
+ year: "numeric",
36
+ timeZone: displayTimeZone
37
+ });
38
+ const subtitleDateFormatter = new Intl.DateTimeFormat(displayLocale, {
39
+ year: "numeric",
40
+ month: "numeric",
41
+ day: "numeric",
42
+ hour: "numeric",
43
+ minute: "2-digit",
44
+ second: "2-digit",
45
+ hour12: true,
46
+ timeZone: displayTimeZone
47
+ });
48
+ const timelinePresets = {
49
+ year: 365,
50
+ "7d": 7,
51
+ "30d": 30,
52
+ "90d": 90,
53
+ all: Number.POSITIVE_INFINITY
54
+ };
55
+ const ignoredWorkspaceEntries = /* @__PURE__ */ new Set([
56
+ ".git",
57
+ ".jj",
58
+ "node_modules",
59
+ "webview-dist",
60
+ ".e2e-runtime"
61
+ ]);
62
+ const distAssets = [
63
+ "index.html",
64
+ "timeline-app.js",
65
+ "timeline-app.css",
66
+ "pierre-worker-portable.js"
67
+ ];
68
+ async function startStandaloneTimelineServer(args) {
69
+ await ensureDistReady();
70
+ const runtime = await createStandaloneRuntime({
71
+ workspacePath: args.workspacePath,
72
+ filePath: args.filePath
73
+ });
74
+ const server = node_http.default.createServer((request, response) => {
75
+ handleRequest({
76
+ request,
77
+ response,
78
+ runtime,
79
+ verbose: args.verbose === true
80
+ });
81
+ });
82
+ await new Promise((resolve, reject) => {
83
+ server.once("error", reject);
84
+ server.listen(args.port || 0, "127.0.0.1", () => {
85
+ server.off("error", reject);
86
+ resolve();
87
+ });
88
+ });
89
+ const address = server.address();
90
+ const url = `http://127.0.0.1:${typeof address === "object" && address ? address.port : args.port || 0}/?standalone=1`;
91
+ if (args.openBrowser !== false) try {
92
+ await openTarget(url);
93
+ } catch (error) {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ process.stderr.write(`Failed to open the browser automatically: ${message}\n`);
96
+ }
97
+ return {
98
+ url,
99
+ async close() {
100
+ await new Promise((resolve, reject) => {
101
+ server.close((error) => {
102
+ if (error) {
103
+ reject(error);
104
+ return;
105
+ }
106
+ resolve();
107
+ });
108
+ });
109
+ }
110
+ };
111
+ }
112
+ async function createStandaloneRuntime(args) {
113
+ const repoContext = await resolveRepoContext(args.workspacePath);
114
+ const absoluteFilePath = node_path.default.resolve(args.filePath);
115
+ const relativePath = node_path.default.relative(repoContext.repoRoot, absoluteFilePath);
116
+ if (!relativePath || relativePath.startsWith("..")) throw new Error(`The file must be inside the repository root: ${repoContext.repoRoot}`);
117
+ const preferencesByPath = /* @__PURE__ */ new Map();
118
+ const fileCache = /* @__PURE__ */ new Map();
119
+ const rangeOverviewCache = /* @__PURE__ */ new Map();
120
+ let activeRelativePath = normalizePath(relativePath);
121
+ return { async handleCommand(command) {
122
+ switch (command.command) {
123
+ case "ready": return emitTimeline();
124
+ case "refresh":
125
+ fileCache.delete(activeRelativePath);
126
+ clearRangeOverviewCache(activeRelativePath);
127
+ return emitTimeline();
128
+ case "switch-file":
129
+ activeRelativePath = normalizePath(command.relativePath);
130
+ await getActiveFileFixture();
131
+ return emitTimeline();
132
+ case "persist-state":
133
+ preferencesByPath.set(activeRelativePath, extractPersistedPreferences(command));
134
+ return [];
135
+ case "select-entry": return [{
136
+ type: "diff-preview",
137
+ payload: getFixturePreview({
138
+ fileFixture: await getActiveFileFixture(),
139
+ fromIndex: command.fromIndex,
140
+ toIndex: command.toIndex,
141
+ comparisonSource: command.comparisonSource
142
+ })
143
+ }];
144
+ case "load-range-overview": {
145
+ const fileFixture = await getActiveFileFixture();
146
+ const normalizedFromIndex = Math.max(0, Math.min(command.fromIndex, command.toIndex));
147
+ const normalizedToIndex = Math.max(normalizedFromIndex, Math.max(command.fromIndex, command.toIndex));
148
+ const comparisonSource = command.comparisonSource === "snapshot" ? "snapshot" : "revision";
149
+ const selectedEntryIndexes = command.selectedEntryIndexes || [];
150
+ const overviewKey = `${activeRelativePath}:${comparisonSource}:${normalizedFromIndex}:${normalizedToIndex}:${selectedEntryIndexes.join(",") || "all"}`;
151
+ let items = rangeOverviewCache.get(overviewKey);
152
+ if (!items) {
153
+ items = await buildRangeOverview({
154
+ repoRoot: repoContext.repoRoot,
155
+ fileFixture,
156
+ currentRelativePath: activeRelativePath,
157
+ fromIndex: normalizedFromIndex,
158
+ toIndex: normalizedToIndex,
159
+ comparisonSource,
160
+ selectedEntryIndexes
161
+ });
162
+ rangeOverviewCache.set(overviewKey, items);
163
+ }
164
+ return [{
165
+ type: "range-overview",
166
+ payload: {
167
+ fromIndex: normalizedFromIndex,
168
+ toIndex: normalizedToIndex,
169
+ comparisonSource,
170
+ selectedEntryIndexes,
171
+ items
172
+ }
173
+ }];
174
+ }
175
+ case "load-range-stack": {
176
+ const normalizedFromIndex = Math.max(0, Math.min(command.fromIndex, command.toIndex));
177
+ const normalizedToIndex = Math.max(normalizedFromIndex, Math.max(command.fromIndex, command.toIndex));
178
+ const comparisonSource = command.comparisonSource === "snapshot" ? "snapshot" : "revision";
179
+ const items = [];
180
+ for (const stackPath of command.relativePaths) {
181
+ const normalizedPath = normalizePath(stackPath);
182
+ const fileFixture = normalizedPath === activeRelativePath ? await getActiveFileFixture() : await getFileFixture(normalizedPath);
183
+ items.push({
184
+ relativePath: normalizedPath,
185
+ preview: getFixturePreview({
186
+ fileFixture,
187
+ fromIndex: normalizedFromIndex,
188
+ toIndex: normalizedToIndex,
189
+ comparisonSource
190
+ })
191
+ });
192
+ }
193
+ return [{
194
+ type: "range-stack-previews",
195
+ payload: {
196
+ fromIndex: normalizedFromIndex,
197
+ toIndex: normalizedToIndex,
198
+ comparisonSource,
199
+ items
200
+ }
201
+ }];
202
+ }
203
+ case "search-history": {
204
+ const entries = (await getActiveFileFixture()).timelineData.entries.filter((entry) => !entry.isWorkingTree);
205
+ const tip = entries[Math.max(0, entries.length - 1)];
206
+ const introducedAt = (entries[Math.max(0, entries.length - 2)] || tip)?.index ?? 0;
207
+ const tipIndex = tip?.index ?? introducedAt;
208
+ const hits = [{
209
+ entryIndex: introducedAt,
210
+ kind: "introduced"
211
+ }];
212
+ if (tipIndex !== introducedAt) hits.push({
213
+ entryIndex: tipIndex,
214
+ kind: "present"
215
+ });
216
+ return [{
217
+ type: "history-search",
218
+ payload: {
219
+ query: command.query,
220
+ introducedAt,
221
+ removedAt: null,
222
+ hits,
223
+ purpose: command.purpose === "sidebar" ? "sidebar" : "history"
224
+ }
225
+ }];
226
+ }
227
+ case "load-revision-tree-files": {
228
+ const fileFixture = await getActiveFileFixture();
229
+ return [{
230
+ type: "revision-tree-files",
231
+ payload: {
232
+ revision: command.revision,
233
+ files: fileFixture.timelineData.workspaceFiles || []
234
+ }
235
+ }];
236
+ }
237
+ case "load-file-oplog": {
238
+ const fileFixture = await getActiveFileFixture();
239
+ if (fileFixture.timelineData.backend !== "jj") return [{
240
+ type: "file-oplog",
241
+ payload: {
242
+ relativePath: fileFixture.timelineData.relativePath,
243
+ entries: []
244
+ }
245
+ }];
246
+ try {
247
+ const { stdout } = await run({
248
+ command: "jj",
249
+ args: [
250
+ "op",
251
+ "log",
252
+ "--limit",
253
+ "80",
254
+ "-T",
255
+ require_file_oplog.JJ_OP_LOG_TEMPLATE
256
+ ],
257
+ cwd: fileFixture.timelineData.workspacePath || process.cwd()
258
+ });
259
+ const fileEntries = [...fileFixture.timelineData.snapshotEntries || [], ...fileFixture.timelineData.entries.filter((entry) => Boolean(entry.operationId))];
260
+ return [{
261
+ type: "file-oplog",
262
+ payload: {
263
+ relativePath: fileFixture.timelineData.relativePath,
264
+ entries: require_file_oplog.filterFileOpLogPeek({
265
+ opLog: require_file_oplog.parseJjOpLogOutput(stdout),
266
+ fileEntries,
267
+ relativePath: fileFixture.timelineData.relativePath,
268
+ limit: 12
269
+ })
270
+ }
271
+ }];
272
+ } catch {
273
+ return [{
274
+ type: "file-oplog",
275
+ payload: {
276
+ relativePath: fileFixture.timelineData.relativePath,
277
+ entries: []
278
+ }
279
+ }];
280
+ }
281
+ }
282
+ case "load-entry-diff-counts": {
283
+ const fileFixture = await getActiveFileFixture();
284
+ return [{
285
+ type: "entry-diff-counts",
286
+ payload: {
287
+ comparisonSource: command.comparisonSource,
288
+ counts: await Promise.all(command.entryIndexes.map(async (entryIndex) => ({
289
+ entryIndex,
290
+ diffCount: await getStandaloneEntryDiffCount({
291
+ repoRoot: repoContext.repoRoot,
292
+ fileFixture,
293
+ entryIndex,
294
+ comparisonSource: command.comparisonSource
295
+ })
296
+ })))
297
+ }
298
+ }];
299
+ }
300
+ case "resolve-nonempty-range": return [{
301
+ type: "resolved-range",
302
+ payload: resolveNonEmptyRange(await getActiveFileFixture(), command.candidateIndexes)
303
+ }];
304
+ case "hydrate-snapshot-entries": {
305
+ const fileFixture = await getActiveFileFixture();
306
+ return [{
307
+ type: "snapshot-entries",
308
+ payload: {
309
+ snapshotEntries: clone(fileFixture.timelineData.snapshotEntries),
310
+ snapshotState: clone(fileFixture.timelineData.snapshotState || { loadedChangeIds: [] })
311
+ }
312
+ }];
313
+ }
314
+ case "open-current-file":
315
+ await openTarget(node_path.default.join(repoContext.repoRoot, activeRelativePath));
316
+ return [];
317
+ case "open-editor-diff":
318
+ case "open-range-files-diff":
319
+ if (command.command === "open-range-files-diff" && command.editorCommand) {
320
+ const fileFixture = await getActiveFileFixture();
321
+ const plan = await buildStandaloneRangeMultiDiffPlan({
322
+ repoRoot: repoContext.repoRoot,
323
+ fileFixture,
324
+ fromIndex: command.fromIndex,
325
+ toIndex: command.toIndex,
326
+ comparisonSource: command.comparisonSource,
327
+ selectedEntryIndexes: command.selectedEntryIndexes || []
328
+ });
329
+ if (plan.files.length) await openMultiDiffWithEditor({
330
+ repoRoot: repoContext.repoRoot,
331
+ editorCommand: command.editorCommand,
332
+ files: plan.files
333
+ });
334
+ return [];
335
+ }
336
+ await openPreviewDocument({
337
+ preview: getFixturePreview({
338
+ fileFixture: await getActiveFileFixture(),
339
+ fromIndex: command.fromIndex,
340
+ toIndex: command.toIndex,
341
+ comparisonSource: command.comparisonSource
342
+ }),
343
+ relativePath: activeRelativePath
344
+ });
345
+ return [];
346
+ case "open-revision-files-diff": {
347
+ if (command.editorCommand) {
348
+ const fileFixture = await getActiveFileFixture();
349
+ const plan = await buildStandaloneEntryMultiDiffPlan({
350
+ repoRoot: repoContext.repoRoot,
351
+ fileFixture,
352
+ entryIndex: command.entryIndex,
353
+ comparisonSource: command.comparisonSource
354
+ });
355
+ if (plan.files.length) await openMultiDiffWithEditor({
356
+ repoRoot: repoContext.repoRoot,
357
+ editorCommand: command.editorCommand,
358
+ files: plan.files
359
+ });
360
+ return [];
361
+ }
362
+ const preview = getUnitPreview({
363
+ fileFixture: await getActiveFileFixture(),
364
+ entryIndex: command.entryIndex,
365
+ comparisonSource: command.comparisonSource
366
+ });
367
+ if (preview) await openPreviewDocument({
368
+ preview,
369
+ relativePath: activeRelativePath
370
+ });
371
+ return [];
372
+ }
373
+ case "open-revision-remote": {
374
+ const fileFixture = await getActiveFileFixture();
375
+ const entry = require_timeline_model.getEntriesForSource(fileFixture.timelineData, command.comparisonSource).find((candidate) => candidate.index === command.entryIndex);
376
+ if (entry?.remoteUrl) await openTarget(entry.remoteUrl);
377
+ return [];
378
+ }
379
+ default: return [];
380
+ }
381
+ } };
382
+ async function emitTimeline() {
383
+ const fileFixture = await getActiveFileFixture();
384
+ const timelineData = withPersistedPreferences(fileFixture.timelineData);
385
+ const defaultFromIndex = Math.max(0, timelineData.defaultIndex - 1);
386
+ const messages = [{
387
+ type: "timeline-data",
388
+ payload: timelineData
389
+ }, {
390
+ type: "diff-preview",
391
+ payload: getFixturePreview({
392
+ fileFixture,
393
+ fromIndex: defaultFromIndex,
394
+ toIndex: timelineData.defaultIndex,
395
+ comparisonSource: timelineData.preferences.comparisonSource || "revision"
396
+ })
397
+ }];
398
+ if (timelineData.backend === "jj") messages.push({
399
+ type: "snapshot-entries",
400
+ payload: {
401
+ snapshotEntries: clone(fileFixture.timelineData.snapshotEntries || []),
402
+ snapshotState: clone(fileFixture.timelineData.snapshotState || { loadedChangeIds: [] })
403
+ }
404
+ });
405
+ return messages;
406
+ }
407
+ async function getActiveFileFixture() {
408
+ return getFileFixture(activeRelativePath);
409
+ }
410
+ async function getFileFixture(relativeFilePath) {
411
+ const normalizedRelativePath = normalizePath(relativeFilePath);
412
+ if (!fileCache.has(normalizedRelativePath)) {
413
+ const nextFixture = await buildFileFixture({
414
+ backend: repoContext.backend,
415
+ relativePath: normalizedRelativePath,
416
+ repoRoot: repoContext.repoRoot,
417
+ remoteBaseUrl: repoContext.remoteBaseUrl
418
+ });
419
+ fileCache.set(normalizedRelativePath, nextFixture);
420
+ }
421
+ return fileCache.get(normalizedRelativePath);
422
+ }
423
+ function withPersistedPreferences(timelineData) {
424
+ const persistedPreferences = preferencesByPath.get(timelineData.relativePath) || {};
425
+ return {
426
+ ...clone(timelineData),
427
+ preferences: {
428
+ ...clone(timelineData.preferences),
429
+ ...persistedPreferences
430
+ }
431
+ };
432
+ }
433
+ function clearRangeOverviewCache(relativeFilePath) {
434
+ for (const cacheKey of rangeOverviewCache.keys()) if (cacheKey.startsWith(`${relativeFilePath}:`)) rangeOverviewCache.delete(cacheKey);
435
+ }
436
+ }
437
+ async function handleRequest(args) {
438
+ try {
439
+ const requestUrl = new URL(args.request.url || "/", "http://127.0.0.1");
440
+ if (args.request.method === "POST" && requestUrl.pathname === "/api/command") {
441
+ const command = await readJsonBody(args.request);
442
+ const messages = await args.runtime.handleCommand(command);
443
+ writeJson(args.response, 200, { messages });
444
+ return;
445
+ }
446
+ if (args.request.method !== "GET" && args.request.method !== "HEAD") {
447
+ writeJson(args.response, 405, { error: "Method Not Allowed" });
448
+ return;
449
+ }
450
+ const targetPath = resolveStaticPath(requestUrl.pathname);
451
+ if (!targetPath) {
452
+ writeJson(args.response, 403, { error: "Forbidden" });
453
+ return;
454
+ }
455
+ if (!node_fs.default.existsSync(targetPath) || !node_fs.default.statSync(targetPath).isFile()) {
456
+ writeJson(args.response, 404, { error: "Not Found" });
457
+ return;
458
+ }
459
+ const body = await node_fs_promises.default.readFile(targetPath);
460
+ args.response.writeHead(200, {
461
+ "content-type": getContentType(targetPath),
462
+ "cache-control": "no-store"
463
+ });
464
+ if (args.request.method === "HEAD") {
465
+ args.response.end();
466
+ return;
467
+ }
468
+ args.response.end(body);
469
+ } catch (error) {
470
+ const message = error instanceof Error ? error.message : String(error);
471
+ if (args.verbose) process.stderr.write(`Standalone timeline request failed: ${message}\n`);
472
+ writeJson(args.response, 500, { error: message });
473
+ }
474
+ }
475
+ async function buildFileFixture(args) {
476
+ const workspaceFiles = await getWorkspaceFiles(args.repoRoot);
477
+ const entries = args.backend === "jj" ? await getJjEntries({
478
+ repoRoot: args.repoRoot,
479
+ relativePath: args.relativePath,
480
+ remoteBaseUrl: args.remoteBaseUrl
481
+ }) : await getGitEntries({
482
+ repoRoot: args.repoRoot,
483
+ relativePath: args.relativePath,
484
+ remoteBaseUrl: args.remoteBaseUrl
485
+ });
486
+ const snapshotEntries = args.backend === "jj" ? await getJjSnapshotEntries({
487
+ repoRoot: args.repoRoot,
488
+ relativePath: args.relativePath,
489
+ revisionEntries: entries,
490
+ remoteBaseUrl: args.remoteBaseUrl
491
+ }) : [];
492
+ const loadedChangeIds = args.backend === "jj" ? [...new Set(entries.map((entry) => entry.changeId).filter(Boolean))] : [];
493
+ const snapshotSourceEntries = args.backend === "jj" ? composeSnapshotEntries({
494
+ revisionEntries: entries,
495
+ snapshotEntries,
496
+ loadedChangeIds: new Set(loadedChangeIds)
497
+ }) : entries;
498
+ const defaultIndex = Math.max(0, entries.length - 1);
499
+ const pathCache = /* @__PURE__ */ new Map();
500
+ const contentCache = /* @__PURE__ */ new Map();
501
+ return {
502
+ timelineData: {
503
+ backend: args.backend,
504
+ workspacePath: args.repoRoot,
505
+ relativePath: args.relativePath,
506
+ fileName: node_path.default.basename(args.relativePath),
507
+ version: require_package.version,
508
+ presets: timelinePresets,
509
+ defaultIndex,
510
+ latestIndex: defaultIndex,
511
+ preferences: {
512
+ sidebarWidth: 280,
513
+ layoutMode: "split",
514
+ contentMode: "diffs",
515
+ comparisonMode: "range",
516
+ comparisonSource: args.backend === "jj" ? "snapshot" : "revision",
517
+ preset: "90d",
518
+ showIntermediateRevisions: true
519
+ },
520
+ workspaceFiles,
521
+ hasIntermediateRevisions: entries.some((entry) => !entry.touchesFile),
522
+ entries,
523
+ snapshotEntries,
524
+ snapshotState: { loadedChangeIds }
525
+ },
526
+ previews: {
527
+ revision: await buildPreviewMap({
528
+ repoRoot: args.repoRoot,
529
+ entries,
530
+ backend: args.backend,
531
+ relativePath: args.relativePath,
532
+ comparisonSource: "revision",
533
+ pathCache,
534
+ contentCache
535
+ }),
536
+ snapshot: args.backend === "jj" ? await buildPreviewMap({
537
+ repoRoot: args.repoRoot,
538
+ entries: snapshotSourceEntries,
539
+ backend: args.backend,
540
+ relativePath: args.relativePath,
541
+ comparisonSource: "snapshot",
542
+ pathCache,
543
+ contentCache
544
+ }) : await buildPreviewMap({
545
+ repoRoot: args.repoRoot,
546
+ entries,
547
+ backend: args.backend,
548
+ relativePath: args.relativePath,
549
+ comparisonSource: "revision",
550
+ pathCache,
551
+ contentCache
552
+ })
553
+ }
554
+ };
555
+ }
556
+ async function getGitEntries(args) {
557
+ const touchingEntries = await getGitHistoryEntries(args);
558
+ const entries = touchingEntries.length >= 2 ? mergeTimelineEntries(await getGitRepositoryEntries(args), touchingEntries) : touchingEntries;
559
+ seedLatestEntryPath(entries, args.relativePath);
560
+ const workingTreeEntry = await makeWorkingTreeEntry({
561
+ repoRoot: args.repoRoot,
562
+ previousEntry: entries.at(-1),
563
+ relativePath: args.relativePath,
564
+ backend: "git",
565
+ remoteBaseUrl: args.remoteBaseUrl
566
+ });
567
+ if (workingTreeEntry) entries.push(workingTreeEntry);
568
+ return reindexEntries(entries);
569
+ }
570
+ async function getGitHistoryEntries(args) {
571
+ const { stdout } = await run({
572
+ command: "git",
573
+ args: [
574
+ "log",
575
+ "--follow",
576
+ "--reverse",
577
+ "--decorate=short",
578
+ "--date=iso-strict",
579
+ "--format=%H%x09%ad%x09%an%x09%D%x09%s",
580
+ "--max-count=200",
581
+ "--",
582
+ args.relativePath
583
+ ],
584
+ cwd: args.repoRoot
585
+ });
586
+ const revisions = stdout.trim().split(/\r?\n/).filter(Boolean);
587
+ const entries = [];
588
+ for (const [index, line] of revisions.entries()) {
589
+ const [revision, authorDate, authorName, branchNames, description] = line.split(" ");
590
+ entries.push(makeEntry({
591
+ id: revision || "",
592
+ index,
593
+ revision: revision || "",
594
+ shortRevision: (revision || "").slice(0, 8),
595
+ changeId: void 0,
596
+ branchNames: require_history_helpers.parseGitBranchNames(branchNames || ""),
597
+ authorDate: authorDate || "",
598
+ authorName: authorName || "",
599
+ description: description || "",
600
+ touchesFile: true,
601
+ isWorkingTree: false,
602
+ filePath: args.relativePath,
603
+ remoteBaseUrl: args.remoteBaseUrl
604
+ }));
605
+ }
606
+ return entries;
607
+ }
608
+ async function getGitRepositoryEntries(args) {
609
+ const { stdout } = await run({
610
+ command: "git",
611
+ args: [
612
+ "log",
613
+ "--reverse",
614
+ "--decorate=short",
615
+ "--date=iso-strict",
616
+ "--format=%H%x09%ad%x09%an%x09%D%x09%s",
617
+ "--max-count=200"
618
+ ],
619
+ cwd: args.repoRoot
620
+ });
621
+ const revisions = stdout.trim().split(/\r?\n/).filter(Boolean);
622
+ const entries = [];
623
+ for (const [index, line] of revisions.entries()) {
624
+ const [revision, authorDate, authorName, branchNames, description] = line.split(" ");
625
+ entries.push(makeEntry({
626
+ id: revision || "",
627
+ index,
628
+ revision: revision || "",
629
+ shortRevision: (revision || "").slice(0, 8),
630
+ changeId: void 0,
631
+ branchNames: require_history_helpers.parseGitBranchNames(branchNames || ""),
632
+ authorDate: authorDate || "",
633
+ authorName: authorName || "",
634
+ description: description || "",
635
+ touchesFile: false,
636
+ isWorkingTree: false,
637
+ filePath: void 0,
638
+ remoteBaseUrl: args.remoteBaseUrl
639
+ }));
640
+ }
641
+ return entries;
642
+ }
643
+ async function getJjEntries(args) {
644
+ const { stdout } = await run({
645
+ command: "jj",
646
+ args: [
647
+ "log",
648
+ "--no-graph",
649
+ "--reversed",
650
+ "--limit",
651
+ "100",
652
+ "-T",
653
+ require_history_helpers.buildJjHistoryLogTemplate()
654
+ ],
655
+ cwd: args.repoRoot
656
+ });
657
+ const revisions = stdout.trim().split(/\r?\n/).filter(Boolean);
658
+ const entries = [];
659
+ for (const [index, line] of revisions.entries()) {
660
+ const parsed = require_history_helpers.parseJjHistoryLine(line);
661
+ if (/^0+$/u.test(parsed.revision || "")) continue;
662
+ const touchesFile = await jjTouchesFile({
663
+ repoRoot: args.repoRoot,
664
+ revision: parsed.revision || "",
665
+ relativePath: args.relativePath
666
+ });
667
+ entries.push(makeEntry({
668
+ ...parsed,
669
+ index,
670
+ touchesFile,
671
+ filePath: args.relativePath,
672
+ remoteBaseUrl: args.remoteBaseUrl
673
+ }));
674
+ }
675
+ const lastEntry = entries.at(-1);
676
+ if (lastEntry && !lastEntry.description) {
677
+ lastEntry.revision = "WORKTREE";
678
+ lastEntry.shortRevision = "Current";
679
+ lastEntry.description = "Working tree";
680
+ lastEntry.isWorkingTree = true;
681
+ lastEntry.changeId = void 0;
682
+ lastEntry.remoteUrl = void 0;
683
+ }
684
+ seedLatestEntryPath(entries, args.relativePath);
685
+ const workingTreeEntry = await makeWorkingTreeEntry({
686
+ repoRoot: args.repoRoot,
687
+ previousEntry: entries.at(-1),
688
+ relativePath: args.relativePath,
689
+ backend: "jj",
690
+ remoteBaseUrl: args.remoteBaseUrl
691
+ });
692
+ if (workingTreeEntry) entries.push(workingTreeEntry);
693
+ return reindexEntries(dedupeAdjacentTimelineEntries(entries));
694
+ }
695
+ async function getJjSnapshotEntries(args) {
696
+ const snapshotEntries = [];
697
+ for (const entry of args.revisionEntries) {
698
+ if (!entry.changeId || entry.isWorkingTree || !entry.touchesFile) continue;
699
+ let stdout = "";
700
+ try {
701
+ ({stdout} = await run({
702
+ command: "jj",
703
+ args: [
704
+ "evolog",
705
+ "--no-graph",
706
+ "--summary",
707
+ "--limit",
708
+ "100",
709
+ "-r",
710
+ entry.revision
711
+ ],
712
+ cwd: args.repoRoot
713
+ }));
714
+ } catch {
715
+ continue;
716
+ }
717
+ const evolutionEntries = require_history_helpers.parseJjEvolutionSummaryEntries(stdout).filter((evolutionEntry) => require_history_helpers.parseJjSummaryChangedPaths(evolutionEntry.summaryLines).includes(args.relativePath)).map((evolutionEntry, evolutionIndex) => makeEntry({
718
+ id: `snapshot:${entry.changeId}:${evolutionEntry.operationId || evolutionEntry.changeKey || evolutionEntry.revision}:${evolutionIndex}`,
719
+ index: 0,
720
+ revision: evolutionEntry.revision,
721
+ shortRevision: require_history_helpers.normalizeSnapshotOperationKey(evolutionEntry.changeKey) || evolutionEntry.revision.slice(0, 8),
722
+ changeId: entry.changeId,
723
+ bookmarkNames: evolutionEntry.bookmarkNames,
724
+ authorDate: normalizeSnapshotAuthorDate(evolutionEntry.authorDate, entry.authorDate),
725
+ authorName: evolutionEntry.authorName || entry.authorName,
726
+ description: normalizeSnapshotDescription(evolutionEntry.description, evolutionEntry.operationDescription),
727
+ touchesFile: true,
728
+ isWorkingTree: false,
729
+ filePath: args.relativePath,
730
+ operationId: evolutionEntry.operationId,
731
+ operationIndex: evolutionEntry.operationIndex,
732
+ operationKey: evolutionEntry.changeKey,
733
+ remoteBaseUrl: args.remoteBaseUrl
734
+ }));
735
+ snapshotEntries.push(...evolutionEntries);
736
+ }
737
+ snapshotEntries.sort((left, right) => left.timestamp - right.timestamp || left.revision.localeCompare(right.revision));
738
+ return reindexEntries(snapshotEntries);
739
+ }
740
+ function composeSnapshotEntries(args) {
741
+ if (!args.loadedChangeIds.size) return args.revisionEntries;
742
+ const snapshotEntriesByChangeId = args.snapshotEntries.reduce((groups, entry) => {
743
+ if (!entry.changeId) return groups;
744
+ const existing = groups.get(entry.changeId) || [];
745
+ existing.push(entry);
746
+ groups.set(entry.changeId, existing);
747
+ return groups;
748
+ }, /* @__PURE__ */ new Map());
749
+ return reindexEntries(args.revisionEntries.flatMap((entry) => {
750
+ if (!entry.changeId || entry.isWorkingTree || !entry.touchesFile || !args.loadedChangeIds.has(entry.changeId)) return [entry];
751
+ return snapshotEntriesByChangeId.get(entry.changeId) || [entry];
752
+ }));
753
+ }
754
+ function seedLatestEntryPath(entries, relativePath) {
755
+ for (const entry of entries) if (!entry.isWorkingTree && entry.filePath) delete entry.filePath;
756
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
757
+ const entry = entries[index];
758
+ if (!entry || entry.isWorkingTree || !entry.touchesFile) continue;
759
+ entry.filePath = relativePath;
760
+ break;
761
+ }
762
+ }
763
+ function mergeTimelineEntries(repositoryEntries, fileEntries) {
764
+ const touchingByRevision = new Map(fileEntries.map((entry) => [entry.revision, entry]));
765
+ const repoIndexes = fileEntries.map((entry) => repositoryEntries.findIndex((candidate) => candidate.revision === entry.revision)).filter((index) => index >= 0);
766
+ if (!repoIndexes.length) return fileEntries;
767
+ const startIndex = Math.min(...repoIndexes);
768
+ const endIndex = Math.max(...repoIndexes);
769
+ const merged = repositoryEntries.slice(startIndex, endIndex + 1).map((entry) => touchingByRevision.get(entry.revision) || entry);
770
+ for (const entry of fileEntries) if (!merged.some((candidate) => candidate.revision === entry.revision)) merged.push(entry);
771
+ return merged.toSorted((left, right) => left.timestamp - right.timestamp || left.revision.localeCompare(right.revision));
772
+ }
773
+ function dedupeAdjacentTimelineEntries(entries) {
774
+ return entries.reduce((deduped, entry) => {
775
+ const previousEntry = deduped.at(-1);
776
+ if (!previousEntry?.changeId || previousEntry.changeId !== entry.changeId) {
777
+ deduped.push(entry);
778
+ return deduped;
779
+ }
780
+ if (!previousEntry.touchesFile && entry.touchesFile) deduped[deduped.length - 1] = entry;
781
+ return deduped;
782
+ }, []);
783
+ }
784
+ async function buildPreviewMap(args) {
785
+ const previews = {};
786
+ for (let toIndex = 1; toIndex < args.entries.length; toIndex += 1) for (let fromIndex = 0; fromIndex < toIndex; fromIndex += 1) previews[`${fromIndex}:${toIndex}`] = await buildPreview({
787
+ repoRoot: args.repoRoot,
788
+ entries: args.entries,
789
+ backend: args.backend,
790
+ relativePath: args.relativePath,
791
+ comparisonSource: args.comparisonSource,
792
+ fromIndex,
793
+ toIndex,
794
+ pathCache: args.pathCache,
795
+ contentCache: args.contentCache
796
+ });
797
+ return previews;
798
+ }
799
+ async function buildPreview(args) {
800
+ const fromEntry = args.entries[args.fromIndex];
801
+ const toEntry = args.entries[args.toIndex];
802
+ const beforePath = await resolveEntryFilePath({
803
+ repoRoot: args.repoRoot,
804
+ backend: args.backend,
805
+ entries: args.entries,
806
+ entryIndex: args.fromIndex,
807
+ relativePath: args.relativePath,
808
+ pathCache: args.pathCache
809
+ });
810
+ const afterPath = await resolveEntryFilePath({
811
+ repoRoot: args.repoRoot,
812
+ backend: args.backend,
813
+ entries: args.entries,
814
+ entryIndex: args.toIndex,
815
+ relativePath: args.relativePath,
816
+ pathCache: args.pathCache
817
+ });
818
+ const beforeText = await getEntryContent({
819
+ repoRoot: args.repoRoot,
820
+ backend: args.backend,
821
+ entries: args.entries,
822
+ entryIndex: args.fromIndex,
823
+ relativePath: args.relativePath,
824
+ pathCache: args.pathCache,
825
+ contentCache: args.contentCache
826
+ });
827
+ const afterText = await getEntryContent({
828
+ repoRoot: args.repoRoot,
829
+ backend: args.backend,
830
+ entries: args.entries,
831
+ entryIndex: args.toIndex,
832
+ relativePath: args.relativePath,
833
+ pathCache: args.pathCache,
834
+ contentCache: args.contentCache
835
+ });
836
+ const stats = require_diff_stats.computeDiffStats(beforeText, afterText);
837
+ return {
838
+ index: args.toIndex,
839
+ title: `${fromEntry?.shortRevision || ""} -> ${toEntry?.shortRevision || ""}`,
840
+ subtitle: toEntry?.isWorkingTree ? "Working tree" : `${subtitleDateFormatter.format(new Date(toEntry?.authorDate || 0))} · ${toEntry?.description || ""}`,
841
+ diffCount: 0,
842
+ additions: stats.additions,
843
+ deletions: stats.deletions,
844
+ hunkCount: stats.hunkCount,
845
+ hasChanges: stats.hasChanges,
846
+ fromIndex: args.fromIndex,
847
+ toIndex: args.toIndex,
848
+ comparisonSource: args.comparisonSource,
849
+ beforePath,
850
+ afterPath,
851
+ beforeText,
852
+ afterText,
853
+ nonTextualDetails: stats.hasChanges ? [] : require_non_textual_details.buildNonTextualDetails({
854
+ beforeText,
855
+ afterText,
856
+ beforePath,
857
+ afterPath,
858
+ isWorkingTree: toEntry?.isWorkingTree
859
+ })
860
+ };
861
+ }
862
+ async function getEntryContent(args) {
863
+ const entry = args.entries[args.entryIndex];
864
+ if (!entry) return "";
865
+ const resolvedPath = entry.isWorkingTree ? args.relativePath : await resolveEntryFilePath(args);
866
+ const cacheKey = `${entry.revision}:${resolvedPath}`;
867
+ if (args.contentCache.has(cacheKey)) return args.contentCache.get(cacheKey) || "";
868
+ let content = "";
869
+ if (entry.isWorkingTree) {
870
+ try {
871
+ content = await node_fs_promises.default.readFile(node_path.default.join(args.repoRoot, args.relativePath), "utf8");
872
+ } catch (error) {
873
+ if (!isMissingFileError(error)) throw error;
874
+ }
875
+ args.contentCache.set(cacheKey, content);
876
+ return content;
877
+ }
878
+ content = args.backend === "jj" ? await showJjFileAtRevision({
879
+ repoRoot: args.repoRoot,
880
+ revision: entry.revision,
881
+ relativePath: resolvedPath
882
+ }) : await showGitFileAtRevision({
883
+ repoRoot: args.repoRoot,
884
+ revision: entry.revision,
885
+ relativePath: resolvedPath
886
+ });
887
+ args.contentCache.set(cacheKey, content);
888
+ return content;
889
+ }
890
+ async function resolveEntryFilePath(args) {
891
+ const entry = args.entries[args.entryIndex];
892
+ if (!entry || entry.isWorkingTree) return args.relativePath;
893
+ if (entry.filePath) return entry.filePath;
894
+ await ensureEntryFilePath(args);
895
+ return entry.filePath || args.relativePath;
896
+ }
897
+ async function ensureEntryFilePath(args) {
898
+ const targetEntry = args.entries[args.entryIndex];
899
+ if (!targetEntry || targetEntry.filePath || targetEntry.isWorkingTree) return;
900
+ let knownIndex = -1;
901
+ for (let index = args.entryIndex + 1; index < args.entries.length; index += 1) if (args.entries[index]?.filePath) {
902
+ knownIndex = index;
903
+ break;
904
+ }
905
+ if (knownIndex < 0) {
906
+ targetEntry.filePath = args.relativePath;
907
+ return;
908
+ }
909
+ let currentPath = args.entries[knownIndex].filePath || args.relativePath;
910
+ for (let index = knownIndex; index > args.entryIndex; index -= 1) {
911
+ const currentEntry = args.entries[index];
912
+ const previousEntry = args.entries[index - 1];
913
+ if (!previousEntry) break;
914
+ if (!previousEntry.filePath) {
915
+ let previousPath = currentPath;
916
+ if (currentEntry.touchesFile && !currentEntry.isWorkingTree) previousPath = await resolvePreviousPathAcrossRevision({
917
+ repoRoot: args.repoRoot,
918
+ backend: args.backend,
919
+ revision: currentEntry.revision,
920
+ currentPath,
921
+ pathCache: args.pathCache
922
+ });
923
+ previousEntry.filePath = previousPath;
924
+ }
925
+ currentPath = previousEntry.filePath || currentPath;
926
+ }
927
+ }
928
+ async function resolvePreviousPathAcrossRevision(args) {
929
+ const cacheKey = `${args.revision}:${args.currentPath}`;
930
+ if (args.pathCache.has(cacheKey)) return args.pathCache.get(cacheKey) || args.currentPath;
931
+ const previousPath = args.backend === "git" ? await resolveGitPreviousPath({
932
+ repoRoot: args.repoRoot,
933
+ revision: args.revision,
934
+ currentPath: args.currentPath
935
+ }) : await resolveJjPreviousPath({
936
+ repoRoot: args.repoRoot,
937
+ revision: args.revision,
938
+ currentPath: args.currentPath
939
+ });
940
+ args.pathCache.set(cacheKey, previousPath);
941
+ return previousPath;
942
+ }
943
+ async function resolveGitPreviousPath(args) {
944
+ const { stdout } = await run({
945
+ command: "git",
946
+ args: [
947
+ "diff-tree",
948
+ "--root",
949
+ "--no-commit-id",
950
+ "--name-status",
951
+ "--find-renames",
952
+ "-r",
953
+ args.revision
954
+ ],
955
+ cwd: args.repoRoot
956
+ });
957
+ for (const line of stdout.split(/\r?\n/u).map((value) => value.trim()).filter(Boolean)) {
958
+ const [, fromPath = "", toPath = ""] = line.split(" ");
959
+ if (toPath === args.currentPath) return fromPath || args.currentPath;
960
+ }
961
+ return args.currentPath;
962
+ }
963
+ async function resolveJjPreviousPath(args) {
964
+ try {
965
+ const { stdout } = await run({
966
+ command: "jj",
967
+ args: [
968
+ "diff",
969
+ "--summary",
970
+ "-r",
971
+ args.revision
972
+ ],
973
+ cwd: args.repoRoot
974
+ });
975
+ const rename = require_history_helpers.parseJjSummaryRenameLines(stdout).find((entry) => entry.toPath === args.currentPath);
976
+ return rename ? rename.fromPath : args.currentPath;
977
+ } catch {
978
+ return args.currentPath;
979
+ }
980
+ }
981
+ async function showGitFileAtRevision(args) {
982
+ try {
983
+ const { stdout } = await run({
984
+ command: "git",
985
+ args: ["show", `${args.revision}:${args.relativePath}`],
986
+ cwd: args.repoRoot
987
+ });
988
+ return stdout;
989
+ } catch (error) {
990
+ if (isMissingFileAtRevisionError(error)) return "";
991
+ throw error;
992
+ }
993
+ }
994
+ async function showJjFileAtRevision(args) {
995
+ try {
996
+ const { stdout } = await run({
997
+ command: "jj",
998
+ args: [
999
+ "file",
1000
+ "show",
1001
+ "-r",
1002
+ args.revision,
1003
+ require_history_helpers.toJjRootFileFileset(args.relativePath)
1004
+ ],
1005
+ cwd: args.repoRoot
1006
+ });
1007
+ return stdout;
1008
+ } catch (error) {
1009
+ if (isMissingFileAtRevisionError(error)) return "";
1010
+ throw error;
1011
+ }
1012
+ }
1013
+ async function jjTouchesFile(args) {
1014
+ const { stdout } = await run({
1015
+ command: "jj",
1016
+ args: [
1017
+ "diff",
1018
+ "--name-only",
1019
+ "-r",
1020
+ args.revision
1021
+ ],
1022
+ cwd: args.repoRoot
1023
+ });
1024
+ return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).includes(args.relativePath);
1025
+ }
1026
+ async function listGitRevisionFiles(args) {
1027
+ const { stdout } = await run({
1028
+ command: "git",
1029
+ args: [
1030
+ "diff-tree",
1031
+ "--root",
1032
+ "--no-commit-id",
1033
+ "--name-only",
1034
+ "-r",
1035
+ args.revision
1036
+ ],
1037
+ cwd: args.repoRoot
1038
+ });
1039
+ return stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
1040
+ }
1041
+ async function resolveGitParentRevision(args) {
1042
+ try {
1043
+ const { stdout } = await run({
1044
+ command: "git",
1045
+ args: ["rev-parse", `${args.revision}^`],
1046
+ cwd: args.repoRoot
1047
+ });
1048
+ return stdout.trim() || void 0;
1049
+ } catch {
1050
+ return;
1051
+ }
1052
+ }
1053
+ async function listGitWorkingTreeFiles(args) {
1054
+ const files = /* @__PURE__ */ new Set();
1055
+ try {
1056
+ const { stdout } = await run({
1057
+ command: "git",
1058
+ args: [
1059
+ "diff",
1060
+ "--name-only",
1061
+ "HEAD",
1062
+ "--"
1063
+ ],
1064
+ cwd: args.repoRoot
1065
+ });
1066
+ for (const relativePath of stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean)) files.add(relativePath);
1067
+ } catch {}
1068
+ return [...files].toSorted((left, right) => left.localeCompare(right));
1069
+ }
1070
+ async function listJjRevisionFiles(args) {
1071
+ const { stdout } = await run({
1072
+ command: "jj",
1073
+ args: [
1074
+ "diff",
1075
+ "--name-only",
1076
+ "-r",
1077
+ args.revision
1078
+ ],
1079
+ cwd: args.repoRoot
1080
+ });
1081
+ return stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
1082
+ }
1083
+ async function listJjChangedFiles(args) {
1084
+ const { stdout } = await run({
1085
+ command: "jj",
1086
+ args: [
1087
+ "diff",
1088
+ "--name-only",
1089
+ "--from",
1090
+ args.base,
1091
+ "--to",
1092
+ args.target
1093
+ ],
1094
+ cwd: args.repoRoot
1095
+ });
1096
+ return stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
1097
+ }
1098
+ async function buildStandaloneEntryMultiDiffPlan(args) {
1099
+ const backend = args.fileFixture.timelineData.backend;
1100
+ const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
1101
+ const entry = sourceEntries[args.entryIndex];
1102
+ if (!entry) return {
1103
+ title: "",
1104
+ files: []
1105
+ };
1106
+ if (backend === "git") {
1107
+ if (entry.isWorkingTree) {
1108
+ const previousEntry = sourceEntries[Math.max(0, args.entryIndex - 1)];
1109
+ if (!previousEntry) return {
1110
+ title: entry.shortRevision,
1111
+ files: []
1112
+ };
1113
+ const files = await listGitWorkingTreeFiles({ repoRoot: args.repoRoot });
1114
+ return {
1115
+ title: `${previousEntry.shortRevision}..${entry.shortRevision}`,
1116
+ files: files.map((relativePath) => ({
1117
+ relativePath,
1118
+ originalRevset: previousEntry.revision,
1119
+ modifiedRevset: "@",
1120
+ backend
1121
+ }))
1122
+ };
1123
+ }
1124
+ const files = await listGitRevisionFiles({
1125
+ repoRoot: args.repoRoot,
1126
+ revision: entry.revision
1127
+ });
1128
+ const parentRevision = await resolveGitParentRevision({
1129
+ repoRoot: args.repoRoot,
1130
+ revision: entry.revision
1131
+ });
1132
+ return {
1133
+ title: entry.shortRevision,
1134
+ files: files.map((relativePath) => ({
1135
+ relativePath,
1136
+ originalRevset: parentRevision || "EMPTY",
1137
+ modifiedRevset: entry.revision,
1138
+ backend
1139
+ }))
1140
+ };
1141
+ }
1142
+ if (entry.isWorkingTree) {
1143
+ const previousEntry = sourceEntries[Math.max(0, args.entryIndex - 1)];
1144
+ if (!previousEntry) return {
1145
+ title: entry.shortRevision,
1146
+ files: []
1147
+ };
1148
+ const files = await listJjChangedFiles({
1149
+ repoRoot: args.repoRoot,
1150
+ base: previousEntry.revision,
1151
+ target: "@"
1152
+ });
1153
+ return {
1154
+ title: `${previousEntry.shortRevision}..${entry.shortRevision}`,
1155
+ files: files.map((relativePath) => ({
1156
+ relativePath,
1157
+ originalRevset: previousEntry.revision,
1158
+ modifiedRevset: "@",
1159
+ backend
1160
+ }))
1161
+ };
1162
+ }
1163
+ const baseRevision = args.comparisonSource === "snapshot" ? sourceEntries[Math.max(0, args.entryIndex - 1)]?.revision || `${entry.revision}-` : `${entry.revision}-`;
1164
+ const files = args.comparisonSource === "snapshot" ? await listJjChangedFiles({
1165
+ repoRoot: args.repoRoot,
1166
+ base: baseRevision,
1167
+ target: entry.revision
1168
+ }) : await listJjRevisionFiles({
1169
+ repoRoot: args.repoRoot,
1170
+ revision: entry.revision
1171
+ });
1172
+ return {
1173
+ title: entry.shortRevision,
1174
+ files: files.map((relativePath) => ({
1175
+ relativePath,
1176
+ originalRevset: baseRevision,
1177
+ modifiedRevset: entry.revision,
1178
+ backend
1179
+ }))
1180
+ };
1181
+ }
1182
+ async function buildStandaloneRangeMultiDiffPlan(args) {
1183
+ const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
1184
+ const fromEntry = sourceEntries[Math.min(args.fromIndex, args.toIndex)];
1185
+ const toEntry = sourceEntries[Math.max(args.fromIndex, args.toIndex)];
1186
+ const title = fromEntry && toEntry ? `${fromEntry.shortRevision}..${toEntry.shortRevision}` : "Selection";
1187
+ if (!args.selectedEntryIndexes.length) return {
1188
+ title,
1189
+ files: []
1190
+ };
1191
+ return {
1192
+ title,
1193
+ files: (await Promise.all(args.selectedEntryIndexes.map((entryIndex) => buildStandaloneEntryMultiDiffPlan({
1194
+ repoRoot: args.repoRoot,
1195
+ fileFixture: args.fileFixture,
1196
+ entryIndex,
1197
+ comparisonSource: args.comparisonSource
1198
+ })))).flatMap((plan) => plan.files)
1199
+ };
1200
+ }
1201
+ async function getStandaloneEntryDiffCount(args) {
1202
+ return (await buildStandaloneEntryMultiDiffPlan(args)).files.length;
1203
+ }
1204
+ async function openMultiDiffWithEditor(args) {
1205
+ const { command, args: baseArgs } = parseEditorCommand(args.editorCommand);
1206
+ const tempRoot = node_path.default.join(node_os.default.tmpdir(), "jj-plus-editor", `${Date.now()}`);
1207
+ await node_fs_promises.default.mkdir(tempRoot, { recursive: true });
1208
+ for (const [index, file] of args.files.entries()) {
1209
+ const originalPath = await materializeEditorDiffSide({
1210
+ repoRoot: args.repoRoot,
1211
+ backend: file.backend,
1212
+ revset: file.originalRevset,
1213
+ relativePath: file.relativePath,
1214
+ tempRoot,
1215
+ suffix: `${index}-original`
1216
+ });
1217
+ const modifiedPath = await materializeEditorDiffSide({
1218
+ repoRoot: args.repoRoot,
1219
+ backend: file.backend,
1220
+ revset: file.modifiedRevset,
1221
+ relativePath: file.relativePath,
1222
+ tempRoot,
1223
+ suffix: `${index}-modified`
1224
+ });
1225
+ await spawnDetached(command, [
1226
+ ...baseArgs,
1227
+ "--reuse-window",
1228
+ "--diff",
1229
+ originalPath,
1230
+ modifiedPath
1231
+ ]);
1232
+ }
1233
+ }
1234
+ async function materializeEditorDiffSide(args) {
1235
+ const safePath = args.relativePath.replace(/[^a-zA-Z0-9._/-]+/g, "_");
1236
+ const targetPath = node_path.default.join(args.tempRoot, `${args.suffix}-${safePath}`);
1237
+ await node_fs_promises.default.mkdir(node_path.default.dirname(targetPath), { recursive: true });
1238
+ const content = await readStandaloneRevsetFile({
1239
+ repoRoot: args.repoRoot,
1240
+ backend: args.backend,
1241
+ revset: args.revset,
1242
+ relativePath: args.relativePath
1243
+ });
1244
+ await node_fs_promises.default.writeFile(targetPath, content, "utf8");
1245
+ return targetPath;
1246
+ }
1247
+ async function readStandaloneRevsetFile(args) {
1248
+ if (args.revset === "EMPTY") return "";
1249
+ if (args.revset === "@") try {
1250
+ return await node_fs_promises.default.readFile(node_path.default.join(args.repoRoot, args.relativePath), "utf8");
1251
+ } catch (error) {
1252
+ if (isMissingFileError(error)) return "";
1253
+ throw error;
1254
+ }
1255
+ return args.backend === "jj" ? showJjFileAtRevision({
1256
+ repoRoot: args.repoRoot,
1257
+ revision: args.revset,
1258
+ relativePath: args.relativePath
1259
+ }) : showGitFileAtRevision({
1260
+ repoRoot: args.repoRoot,
1261
+ revision: args.revset,
1262
+ relativePath: args.relativePath
1263
+ });
1264
+ }
1265
+ function parseEditorCommand(editorCommand) {
1266
+ const parts = editorCommand.trim().split(/\s+/u).filter(Boolean);
1267
+ if (!parts.length) throw new Error("Missing editor command.");
1268
+ return {
1269
+ command: parts[0],
1270
+ args: parts.slice(1)
1271
+ };
1272
+ }
1273
+ async function spawnDetached(command, args) {
1274
+ await new Promise((resolve, reject) => {
1275
+ const child = (0, node_child_process.spawn)(command, args, {
1276
+ detached: true,
1277
+ stdio: "ignore"
1278
+ });
1279
+ child.once("error", reject);
1280
+ child.once("spawn", () => {
1281
+ child.unref();
1282
+ resolve();
1283
+ });
1284
+ });
1285
+ }
1286
+ async function buildRangeOverview(args) {
1287
+ const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
1288
+ const workspaceFileSet = new Set(args.fileFixture.timelineData.workspaceFiles || []);
1289
+ const countedPaths = /* @__PURE__ */ new Map();
1290
+ const entryIndexesToScan = args.selectedEntryIndexes?.length ? args.selectedEntryIndexes : Array.from({ length: args.toIndex - args.fromIndex }, (_, index) => args.fromIndex + index + 1);
1291
+ for (const index of entryIndexesToScan) {
1292
+ const entry = sourceEntries[index];
1293
+ if (!entry) continue;
1294
+ const changedFiles = args.fileFixture.timelineData.backend === "git" ? entry.isWorkingTree ? await listGitWorkingTreeFiles({ repoRoot: args.repoRoot }) : await listGitRevisionFiles({
1295
+ repoRoot: args.repoRoot,
1296
+ revision: entry.revision
1297
+ }) : entry.isWorkingTree ? await listJjRevisionFiles({
1298
+ repoRoot: args.repoRoot,
1299
+ revision: "@"
1300
+ }) : await listJjRevisionFiles({
1301
+ repoRoot: args.repoRoot,
1302
+ revision: entry.revision
1303
+ });
1304
+ for (const relativePath of changedFiles) {
1305
+ if (!relativePath || !workspaceFileSet.has(relativePath) && relativePath !== args.currentRelativePath) continue;
1306
+ countedPaths.set(relativePath, (countedPaths.get(relativePath) || 0) + 1);
1307
+ }
1308
+ }
1309
+ return [...countedPaths.entries()].map(([relativePath, changeCount]) => ({
1310
+ relativePath,
1311
+ changeCount,
1312
+ isCurrentFile: relativePath === args.currentRelativePath
1313
+ })).toSorted((left, right) => {
1314
+ if (left.changeCount !== right.changeCount) return right.changeCount - left.changeCount;
1315
+ if (left.isCurrentFile !== right.isCurrentFile) return Number(right.isCurrentFile) - Number(left.isCurrentFile);
1316
+ return left.relativePath.localeCompare(right.relativePath);
1317
+ }).slice(0, 150);
1318
+ }
1319
+ async function getWorkspaceFiles(repoRoot) {
1320
+ try {
1321
+ const { stdout } = await run({
1322
+ command: "jj",
1323
+ args: ["file", "list"],
1324
+ cwd: repoRoot
1325
+ });
1326
+ return stdout.split(/\r?\n/).filter(Boolean).toSorted((left, right) => left.localeCompare(right));
1327
+ } catch {
1328
+ try {
1329
+ const { stdout } = await run({
1330
+ command: "git",
1331
+ args: ["ls-files", "--cached"],
1332
+ cwd: repoRoot
1333
+ });
1334
+ return stdout.split(/\r?\n/).filter(Boolean).toSorted((left, right) => left.localeCompare(right));
1335
+ } catch {
1336
+ const files = [];
1337
+ await collectWorkspaceFiles({
1338
+ repoRoot,
1339
+ currentDir: repoRoot,
1340
+ files
1341
+ });
1342
+ return files.toSorted((left, right) => left.localeCompare(right));
1343
+ }
1344
+ }
1345
+ }
1346
+ async function collectWorkspaceFiles(args) {
1347
+ const entries = await node_fs_promises.default.readdir(args.currentDir, { withFileTypes: true });
1348
+ for (const entry of entries) {
1349
+ if (ignoredWorkspaceEntries.has(entry.name)) continue;
1350
+ const absolutePath = node_path.default.join(args.currentDir, entry.name);
1351
+ if (entry.isDirectory()) {
1352
+ await collectWorkspaceFiles({
1353
+ repoRoot: args.repoRoot,
1354
+ currentDir: absolutePath,
1355
+ files: args.files
1356
+ });
1357
+ continue;
1358
+ }
1359
+ if (!entry.isFile()) continue;
1360
+ args.files.push(normalizePath(node_path.default.relative(args.repoRoot, absolutePath)));
1361
+ }
1362
+ }
1363
+ async function makeWorkingTreeEntry(args) {
1364
+ let content = "";
1365
+ let stats = null;
1366
+ try {
1367
+ stats = await node_fs_promises.default.stat(node_path.default.join(args.repoRoot, args.relativePath));
1368
+ content = await node_fs_promises.default.readFile(node_path.default.join(args.repoRoot, args.relativePath), "utf8");
1369
+ } catch (error) {
1370
+ if (!isMissingFileError(error)) throw error;
1371
+ }
1372
+ const previousContent = args.previousEntry ? args.previousEntry.isWorkingTree ? content : args.backend === "jj" ? await showJjFileAtRevision({
1373
+ repoRoot: args.repoRoot,
1374
+ revision: args.previousEntry.revision,
1375
+ relativePath: args.previousEntry.filePath || args.relativePath
1376
+ }) : await showGitFileAtRevision({
1377
+ repoRoot: args.repoRoot,
1378
+ revision: args.previousEntry.revision,
1379
+ relativePath: args.previousEntry.filePath || args.relativePath
1380
+ }) : "";
1381
+ if (content === previousContent) return null;
1382
+ return makeEntry({
1383
+ id: `working-tree:${args.relativePath}`,
1384
+ index: args.previousEntry ? (args.previousEntry.index || 0) + 1 : 0,
1385
+ revision: "WORKTREE",
1386
+ shortRevision: "Current",
1387
+ changeId: void 0,
1388
+ authorDate: (stats?.mtime || /* @__PURE__ */ new Date()).toISOString(),
1389
+ authorName: process.env.GIT_AUTHOR_NAME || process.env.USER || "CLI User",
1390
+ description: "Working tree",
1391
+ touchesFile: true,
1392
+ isWorkingTree: true,
1393
+ filePath: args.relativePath,
1394
+ remoteBaseUrl: args.remoteBaseUrl
1395
+ });
1396
+ }
1397
+ function makeEntry(args) {
1398
+ const timestamp = Date.parse(args.authorDate);
1399
+ return {
1400
+ id: args.id,
1401
+ index: args.index,
1402
+ revision: args.revision,
1403
+ shortRevision: args.shortRevision,
1404
+ changeId: args.changeId,
1405
+ bookmarkNames: args.bookmarkNames,
1406
+ branchNames: args.branchNames,
1407
+ authorDate: args.authorDate,
1408
+ authorName: args.authorName,
1409
+ description: args.description || "",
1410
+ isWorkingTree: args.isWorkingTree,
1411
+ touchesFile: args.touchesFile,
1412
+ timestamp,
1413
+ filePath: args.filePath,
1414
+ operationId: args.operationId,
1415
+ operationIndex: args.operationIndex,
1416
+ operationKey: args.operationKey,
1417
+ monthLabel: monthFormatter.format(timestamp),
1418
+ shortDate: shortDateFormatter.format(timestamp),
1419
+ relativeDate: relativeTime(timestamp),
1420
+ hasPreviousEntry: args.index > 0,
1421
+ remoteUrl: args.isWorkingTree || !args.remoteBaseUrl ? void 0 : `${args.remoteBaseUrl}/commit/${args.revision}`,
1422
+ ...args.isEmpty ? { isEmpty: true } : {},
1423
+ ...args.hasConflict ? { hasConflict: true } : {},
1424
+ ...args.isImmutable ? { isImmutable: true } : {}
1425
+ };
1426
+ }
1427
+ function reindexEntries(entries) {
1428
+ return entries.map((entry, index) => ({
1429
+ ...entry,
1430
+ index,
1431
+ hasPreviousEntry: index > 0
1432
+ }));
1433
+ }
1434
+ function normalizeSnapshotAuthorDate(authorDate, fallbackAuthorDate) {
1435
+ const value = String(authorDate || "").trim();
1436
+ if (value && !Number.isNaN(Date.parse(value))) return value;
1437
+ return fallbackAuthorDate;
1438
+ }
1439
+ function normalizeSnapshotDescription(description, operationDescription) {
1440
+ const trimmed = String(description || "").trim();
1441
+ if (!trimmed || trimmed === "(no description set)" || trimmed === "(empty) (no description set)") return operationDescription || "Snapshot";
1442
+ return trimmed;
1443
+ }
1444
+ function relativeTime(timestamp) {
1445
+ const deltaHours = Math.round((Date.now() - timestamp) / 36e5);
1446
+ if (deltaHours <= 1) return "this minute";
1447
+ if (deltaHours < 36) return "yesterday";
1448
+ if (deltaHours < 720) return `${Math.round(deltaHours / 24)} days ago`;
1449
+ return `${Math.round(deltaHours / 720)} months ago`;
1450
+ }
1451
+ async function resolveRepoContext(workspacePath) {
1452
+ const [gitResult, jjResult] = await Promise.allSettled([run({
1453
+ command: "git",
1454
+ args: ["rev-parse", "--show-toplevel"],
1455
+ cwd: workspacePath
1456
+ }), run({
1457
+ command: "jj",
1458
+ args: ["root"],
1459
+ cwd: workspacePath
1460
+ })]);
1461
+ const gitRoot = gitResult.status === "fulfilled" ? gitResult.value.stdout.trim() : void 0;
1462
+ const jjRoot = jjResult.status === "fulfilled" ? jjResult.value.stdout.trim() : void 0;
1463
+ const backend = require_history_helpers.resolvePreferredHistoryBackend({
1464
+ workspacePath,
1465
+ gitRoot,
1466
+ jjRoot
1467
+ });
1468
+ if (backend === "jj" && jjRoot) return {
1469
+ backend: "jj",
1470
+ repoRoot: jjRoot,
1471
+ remoteBaseUrl: await resolveRemoteBaseUrl(jjRoot)
1472
+ };
1473
+ if (backend === "git" && gitRoot) return {
1474
+ backend: "git",
1475
+ repoRoot: gitRoot,
1476
+ remoteBaseUrl: await resolveRemoteBaseUrl(gitRoot)
1477
+ };
1478
+ throw new Error("Standalone timeline requires a Git or JJ repository.");
1479
+ }
1480
+ async function resolveRemoteBaseUrl(repoRoot) {
1481
+ try {
1482
+ const { stdout } = await run({
1483
+ command: "git",
1484
+ args: [
1485
+ "remote",
1486
+ "get-url",
1487
+ "origin"
1488
+ ],
1489
+ cwd: repoRoot
1490
+ });
1491
+ return require_history_helpers.getGitHubRemoteBaseUrl(stdout.trim());
1492
+ } catch {
1493
+ return;
1494
+ }
1495
+ }
1496
+ function extractPersistedPreferences(command) {
1497
+ return {
1498
+ sidebarWidth: command.sidebarWidth,
1499
+ sidebarCollapsed: command.sidebarCollapsed,
1500
+ timelinePaneHeight: command.timelinePaneHeight,
1501
+ timelinePaneCollapsed: command.timelinePaneCollapsed,
1502
+ layoutMode: command.layoutMode,
1503
+ contentMode: command.contentMode,
1504
+ comparisonMode: command.comparisonMode,
1505
+ comparisonSource: command.comparisonSource,
1506
+ showIntermediateRevisions: command.showIntermediateRevisions,
1507
+ preset: command.preset,
1508
+ themePreference: command.themePreference,
1509
+ heatmapOpen: command.heatmapOpen === true,
1510
+ lineDiffType: command.lineDiffType
1511
+ };
1512
+ }
1513
+ function getFixturePreview(args) {
1514
+ const normalizedFromIndex = Math.min(args.fromIndex, args.toIndex);
1515
+ const normalizedToIndex = Math.max(args.fromIndex, args.toIndex);
1516
+ const key = `${normalizedFromIndex}:${normalizedToIndex}`;
1517
+ const preview = (args.fileFixture.previews[args.comparisonSource] || args.fileFixture.previews.revision || {})[key];
1518
+ if (preview) return clone(preview);
1519
+ return {
1520
+ index: normalizedToIndex,
1521
+ title: "No diff available",
1522
+ subtitle: "",
1523
+ diffCount: 0,
1524
+ additions: 0,
1525
+ deletions: 0,
1526
+ hunkCount: 0,
1527
+ hasChanges: false,
1528
+ fromIndex: normalizedFromIndex,
1529
+ toIndex: normalizedToIndex,
1530
+ comparisonSource: args.comparisonSource,
1531
+ beforePath: "",
1532
+ afterPath: "",
1533
+ beforeText: "",
1534
+ afterText: "",
1535
+ nonTextualDetails: []
1536
+ };
1537
+ }
1538
+ function resolveNonEmptyRange(fileFixture, candidateIndexes) {
1539
+ const previewMap = fileFixture.previews.revision || {};
1540
+ for (let index = candidateIndexes.length - 1; index > 0; index -= 1) {
1541
+ const fromIndex = candidateIndexes[index - 1];
1542
+ const toIndex = candidateIndexes[index];
1543
+ if (previewMap[`${Math.min(fromIndex, toIndex)}:${Math.max(fromIndex, toIndex)}`]?.hasChanges) return {
1544
+ fromIndex,
1545
+ toIndex
1546
+ };
1547
+ }
1548
+ return null;
1549
+ }
1550
+ function getUnitPreview(args) {
1551
+ const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
1552
+ const currentIndex = sourceEntries.findIndex((entry) => entry.index === args.entryIndex);
1553
+ if (currentIndex <= 0) return null;
1554
+ return getFixturePreview({
1555
+ fileFixture: args.fileFixture,
1556
+ fromIndex: sourceEntries[currentIndex - 1].index || 0,
1557
+ toIndex: sourceEntries[currentIndex].index || 0,
1558
+ comparisonSource: args.comparisonSource
1559
+ });
1560
+ }
1561
+ async function openPreviewDocument(args) {
1562
+ const previewDirectory = node_path.default.join(node_os.default.tmpdir(), "jj-plus-previews");
1563
+ await node_fs_promises.default.mkdir(previewDirectory, { recursive: true });
1564
+ const sanitizedPath = args.relativePath.replace(/[^a-zA-Z0-9._-]+/g, "_");
1565
+ const previewPath = node_path.default.join(previewDirectory, `${Date.now()}-${sanitizedPath}.diff.txt`);
1566
+ await node_fs_promises.default.writeFile(previewPath, renderPreviewText(args.preview, args.relativePath), "utf8");
1567
+ await openTarget(previewPath);
1568
+ }
1569
+ function renderPreviewText(preview, relativePath) {
1570
+ const summary = `+${preview.additions} -${preview.deletions} ${preview.hunkCount} hunks`;
1571
+ const detailLines = preview.nonTextualDetails?.length ? [...preview.nonTextualDetails, ""] : [];
1572
+ const pathLines = [
1573
+ `--- ${preview.beforePath || relativePath}`,
1574
+ `+++ ${preview.afterPath || relativePath}`,
1575
+ ""
1576
+ ];
1577
+ const contentLines = [
1578
+ "===== before =====",
1579
+ preview.beforeText,
1580
+ "===== after =====",
1581
+ preview.afterText
1582
+ ];
1583
+ return [
1584
+ relativePath,
1585
+ preview.title,
1586
+ preview.subtitle,
1587
+ summary,
1588
+ "",
1589
+ ...detailLines,
1590
+ ...pathLines,
1591
+ ...contentLines,
1592
+ ""
1593
+ ].join("\n");
1594
+ }
1595
+ async function openTarget(target) {
1596
+ const launcher = getSystemLauncher(target);
1597
+ await new Promise((resolve, reject) => {
1598
+ const child = (0, node_child_process.spawn)(launcher.command, launcher.args, {
1599
+ detached: true,
1600
+ stdio: "ignore"
1601
+ });
1602
+ child.once("error", reject);
1603
+ child.once("spawn", () => {
1604
+ child.unref();
1605
+ resolve();
1606
+ });
1607
+ });
1608
+ }
1609
+ function getSystemLauncher(target) {
1610
+ if (process.platform === "darwin") return {
1611
+ command: "open",
1612
+ args: [target]
1613
+ };
1614
+ if (process.platform === "win32") return {
1615
+ command: "cmd",
1616
+ args: [
1617
+ "/c",
1618
+ "start",
1619
+ "",
1620
+ target
1621
+ ]
1622
+ };
1623
+ return {
1624
+ command: "xdg-open",
1625
+ args: [target]
1626
+ };
1627
+ }
1628
+ async function ensureDistReady() {
1629
+ const assetPaths = distAssets.map((assetPath) => node_path.default.join(distDir, assetPath));
1630
+ if (!assetPaths.every((assetPath) => node_fs.default.existsSync(assetPath)) || await isDistStale(assetPaths)) await buildWebviewDist();
1631
+ if (!assetPaths.every((assetPath) => node_fs.default.existsSync(assetPath))) throw new Error("Missing webview-dist assets. Run pnpm build:webview before launching the standalone timeline.");
1632
+ }
1633
+ async function isDistStale(assetPaths) {
1634
+ const sourcePaths = await collectBuildInputFiles(node_path.default.join(rootDir, "webview"));
1635
+ return (sourcePaths.length ? Math.max(...await Promise.all(sourcePaths.map((filePath) => node_fs_promises.default.stat(filePath).then((stats) => stats.mtimeMs)))) : 0) > Math.min(...await Promise.all(assetPaths.map((assetPath) => node_fs_promises.default.stat(assetPath).then((stats) => stats.mtimeMs))));
1636
+ }
1637
+ async function collectBuildInputFiles(currentPath) {
1638
+ const entries = await node_fs_promises.default.readdir(currentPath, { withFileTypes: true });
1639
+ const filePaths = [];
1640
+ for (const entry of entries) {
1641
+ const absolutePath = node_path.default.join(currentPath, entry.name);
1642
+ if (entry.isDirectory()) {
1643
+ filePaths.push(...await collectBuildInputFiles(absolutePath));
1644
+ continue;
1645
+ }
1646
+ if (entry.isFile()) filePaths.push(absolutePath);
1647
+ }
1648
+ return filePaths;
1649
+ }
1650
+ async function buildWebviewDist() {
1651
+ const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
1652
+ try {
1653
+ await run({
1654
+ command: pnpmCommand,
1655
+ args: ["build:webview"],
1656
+ cwd: rootDir
1657
+ });
1658
+ return;
1659
+ } catch (error) {
1660
+ if ((error && typeof error === "object" && "code" in error ? error.code : void 0) === "ENOENT") try {
1661
+ await run({
1662
+ command: "corepack",
1663
+ args: ["pnpm", "build:webview"],
1664
+ cwd: rootDir
1665
+ });
1666
+ return;
1667
+ } catch {}
1668
+ const cause = error instanceof Error ? error : new Error(String(error));
1669
+ cause.message = `Failed to build the standalone webview assets automatically: ${cause.message}`;
1670
+ throw cause;
1671
+ }
1672
+ }
1673
+ function resolveStaticPath(requestPath) {
1674
+ const decodedPath = decodeURIComponent(requestPath === "/" ? "/index.html" : requestPath);
1675
+ const candidatePath = node_path.default.normalize(node_path.default.join(distDir, decodedPath));
1676
+ if (candidatePath !== distDir && !candidatePath.startsWith(`${distDir}${node_path.default.sep}`)) return null;
1677
+ return candidatePath;
1678
+ }
1679
+ function getContentType(filePath) {
1680
+ const extension = node_path.default.extname(filePath).toLowerCase();
1681
+ if (extension === ".html") return "text/html; charset=utf-8";
1682
+ if (extension === ".js") return "application/javascript; charset=utf-8";
1683
+ if (extension === ".css") return "text/css; charset=utf-8";
1684
+ if (extension === ".json") return "application/json; charset=utf-8";
1685
+ if (extension === ".svg") return "image/svg+xml";
1686
+ return "application/octet-stream";
1687
+ }
1688
+ async function readJsonBody(request) {
1689
+ const chunks = [];
1690
+ let totalBytes = 0;
1691
+ for await (const chunk of request) {
1692
+ const bufferChunk = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
1693
+ totalBytes += bufferChunk.length;
1694
+ if (totalBytes > 1048576) throw new Error("Request body is too large.");
1695
+ chunks.push(bufferChunk);
1696
+ }
1697
+ const text = Buffer.concat(chunks).toString("utf8");
1698
+ return text ? JSON.parse(text) : {};
1699
+ }
1700
+ function writeJson(response, statusCode, payload) {
1701
+ response.writeHead(statusCode, {
1702
+ "content-type": "application/json; charset=utf-8",
1703
+ "cache-control": "no-store"
1704
+ });
1705
+ response.end(JSON.stringify(payload));
1706
+ }
1707
+ function isMissingFileAtRevisionError(error) {
1708
+ const message = error instanceof Error ? error.message : String(error);
1709
+ return /exists on disk, but not in/i.test(message) || /path .* does not exist in/i.test(message) || /no such path/i.test(message) || /no matching entries/i.test(message) || /No such file or directory/i.test(message) || /Path .* not found/i.test(message);
1710
+ }
1711
+ function isMissingFileError(error) {
1712
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
1713
+ }
1714
+ function normalizePath(relativePath) {
1715
+ return relativePath.split(node_path.default.sep).join("/");
1716
+ }
1717
+ function clone(value) {
1718
+ return JSON.parse(JSON.stringify(value));
1719
+ }
1720
+ async function run(args) {
1721
+ return execFileAsync(args.command, args.args, {
1722
+ cwd: args.cwd,
1723
+ env: {
1724
+ ...process.env,
1725
+ ...args.env
1726
+ }
1727
+ });
1728
+ }
1729
+ //#endregion
1730
+ exports.renderPreviewText = renderPreviewText;
1731
+ exports.startStandaloneTimelineServer = startStandaloneTimelineServer;
1732
+
1733
+ //# sourceMappingURL=server.cjs.map