@hienlh/ppm 0.20.9 → 0.21.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 (968) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/CLAUDE.md +42 -1
  3. package/LICENSE +5 -0
  4. package/THIRD-PARTY-NOTICES.md +83 -0
  5. package/assets/skills/ppm/SKILL.md +1 -1
  6. package/assets/skills/ppm/references/http-api.md +1 -1
  7. package/bun.lock +38 -7
  8. package/dist/web/assets/KaTeX_Size3-Regular-gV2CO0n9.woff2 +0 -0
  9. package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +1 -0
  10. package/dist/web/assets/ai-resource-editor-F09pf8Hd.js +1 -0
  11. package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +1 -0
  12. package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +1 -0
  13. package/dist/web/assets/{angular-html-CTx8qAkv.js → angular-html-DkR4iCth.js} +1 -1
  14. package/dist/web/assets/{angular-ts-BhBGUAnu.js → angular-ts-DcyL4Chx.js} +1 -1
  15. package/dist/web/assets/api-client-BUCo1lms.js +1 -0
  16. package/dist/web/assets/{api-settings-CqiVKFUX.js → api-settings-BcNjZIvG.js} +1 -1
  17. package/dist/web/assets/{api-themes-DtLXqXpC.js → api-themes-DBfyVUtX.js} +1 -1
  18. package/dist/web/assets/{apl-Dec3pSzF.js → apl-BVlrVtjt.js} +1 -1
  19. package/dist/web/assets/appearance-settings-section-whgnq3ss.js +1 -0
  20. package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +1 -0
  21. package/dist/web/assets/{astro-Cb_3-S86.js → astro-MjJlozLj.js} +1 -1
  22. package/dist/web/assets/audio-preview-Bc54GCAV.js +1 -0
  23. package/dist/web/assets/{blade-1QMj5JxI.js → blade-DnJWi1fz.js} +1 -1
  24. package/dist/web/assets/{bsl-D0bT3Wjp.js → bsl-DCmJxSIz.js} +1 -1
  25. package/dist/web/assets/{button-DhnNoqpV.js → button-VW4QGKCn.js} +1 -1
  26. package/dist/web/assets/{cairo-DTuGb2w7.js → cairo-C8dDenub.js} +1 -1
  27. package/dist/web/assets/chat-tab-Cr6_KSGO.js +15 -0
  28. package/dist/web/assets/{cobol-DSx_Nb7T.js → cobol-DPx_nifl.js} +1 -1
  29. package/dist/web/assets/code-editor-DfmlGamv.js +7 -0
  30. package/dist/web/assets/codex-daily-guard-CfybHv_c.js +1 -0
  31. package/dist/web/assets/{coffee-DBcQ7DKB.js → coffee-DvFzqhgN.js} +1 -1
  32. package/dist/web/assets/conflict-editor-DenQUOT2.js +19 -0
  33. package/dist/web/assets/context-menu-DOODKZdF.js +1 -0
  34. package/dist/web/assets/{cpp-Bj1azGr-.js → cpp-BLJN0umi.js} +1 -1
  35. package/dist/web/assets/{crystal-CCgkDI0i.js → crystal-BXR-9fGw.js} +1 -1
  36. package/dist/web/assets/csv-preview-CClrJOuR.js +2 -0
  37. package/dist/web/assets/{data-grid-overlay-editor-C2NAQ-88.js → data-grid-overlay-editor-D4fhcMfT.js} +1 -1
  38. package/dist/web/assets/database-viewer-DItjm8wB.js +1 -0
  39. package/dist/web/assets/dialog-DQTn55Mt.js +1 -0
  40. package/dist/web/assets/diff-viewer-BpNj7pN9.js +4 -0
  41. package/dist/web/assets/docx-preview-DYBkiUaE.js +12 -0
  42. package/dist/web/assets/{edge-ClBfeO7z.js → edge-rWOlCmXT.js} +1 -1
  43. package/dist/web/assets/{edit-diff-preview-CRjufuw_.js → edit-diff-preview-COtmo54G.js} +1 -1
  44. package/dist/web/assets/editor-font-BDrdiGf1.js +1 -0
  45. package/dist/web/assets/editor-language-service-DxkZ51E5.js +1 -0
  46. package/dist/web/assets/{elixir-ir__-3kt.js → elixir-E7Y82okW.js} +1 -1
  47. package/dist/web/assets/{elm-CUv-xMuU.js → elm-DTPGbc-w.js} +1 -1
  48. package/dist/web/assets/{erb-DcRwHyUq.js → erb-DHqLHrE6.js} +1 -1
  49. package/dist/web/assets/{erlang-DW-GAdmv.js → erlang-Byg0nNiV.js} +1 -1
  50. package/dist/web/assets/{esm-DBm5LSCw.js → esm-D_DmOM7n.js} +1 -1
  51. package/dist/web/assets/explorer-body-D-5z464e.js +2 -0
  52. package/dist/web/assets/explorer-window-content-7-1wuRul.js +1 -0
  53. package/dist/web/assets/extension-manager-section-BhwB6nqr.js +1 -0
  54. package/dist/web/assets/extension-webview-DI4VL2tJ.js +12 -0
  55. package/dist/web/assets/file-icons-EzPL0q-k.js +1 -0
  56. package/dist/web/assets/file-icons.generated-CfaJuvZu.css +1 -0
  57. package/dist/web/assets/file-store-DMABxOkV.js +1 -0
  58. package/dist/web/assets/files-settings-section-CZzABuKz.js +1 -0
  59. package/dist/web/assets/format-file-meta-DOpzJJhS.js +1 -0
  60. package/dist/web/assets/{fortran-fixed-form-DrQbMSkt.js → fortran-fixed-form-CY1n0Xf6.js} +1 -1
  61. package/dist/web/assets/{fsharp-DvDj84Ys.js → fsharp-_vEDSCJz.js} +1 -1
  62. package/dist/web/assets/{gdresource-CPgY7R2s.js → gdresource-B6XT0wAC.js} +1 -1
  63. package/dist/web/assets/geist-cyrillic-ext-wght-italic-BYAq_K0C.woff2 +0 -0
  64. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  65. package/dist/web/assets/geist-cyrillic-wght-italic-Da0gXVk3.woff2 +0 -0
  66. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  67. package/dist/web/assets/geist-latin-ext-wght-italic-XgKbg5n7.woff2 +0 -0
  68. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  69. package/dist/web/assets/geist-latin-wght-italic-dTnRTFFJ.woff2 +0 -0
  70. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  71. package/dist/web/assets/geist-vietnamese-wght-italic-0fly6zkB.woff2 +0 -0
  72. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  73. package/dist/web/assets/general-settings-section-BcxwiZ0X.js +1 -0
  74. package/dist/web/assets/{git-commit-NJ9iGbTd.js → git-commit-Wk9ovq59.js} +1 -1
  75. package/dist/web/assets/git-log-panel-CRN4JVOx.js +1 -0
  76. package/dist/web/assets/{git-rebase-DBSy9OKE.js → git-rebase-DSarGPvK.js} +1 -1
  77. package/dist/web/assets/git-repo-store-Dy69YCHN.js +1 -0
  78. package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +1 -0
  79. package/dist/web/assets/glide-data-grid-BvgAqSUr.js +141 -0
  80. package/dist/web/assets/{glimmer-js-EGWSSdwJ.js → glimmer-js-CkYI35_P.js} +1 -1
  81. package/dist/web/assets/{glimmer-ts-BpfXRYRV.js → glimmer-ts-BqmIZcds.js} +1 -1
  82. package/dist/web/assets/{glsl-BtWQ4j8X.js → glsl-Be-yBCF5.js} +1 -1
  83. package/dist/web/assets/{graphql-DHR5AO2c.js → graphql-a5DrENHY.js} +1 -1
  84. package/dist/web/assets/group-chat-tab-DFa5mAx5.js +1 -0
  85. package/dist/web/assets/{hack-A3iOItmC.js → hack-BLAg20aD.js} +1 -1
  86. package/dist/web/assets/{haml-DXA8NXfZ.js → haml-Dj6bGOQH.js} +1 -1
  87. package/dist/web/assets/{handlebars-BdIFK4pP.js → handlebars-dmEa36Oh.js} +1 -1
  88. package/dist/web/assets/{html-derivative-Cd_vDV4L.js → html-derivative-lLi3-RNT.js} +1 -1
  89. package/dist/web/assets/{html-BTA4eRkb.js → html-yacZYzSE.js} +1 -1
  90. package/dist/web/assets/{http-CMY5fp9k.js → http-1KaxcE4e.js} +1 -1
  91. package/dist/web/assets/{hurl-DJZLRi6K.js → hurl-ST-dEEZR.js} +1 -1
  92. package/dist/web/assets/{hxml-C2EdtM37.js → hxml-CpDtuq__.js} +1 -1
  93. package/dist/web/assets/icons-DQjqk0lJ.js +1 -0
  94. package/dist/web/assets/image-preview-CJVxL0uH.js +1 -0
  95. package/dist/web/assets/index-D7DbXD3G.css +1 -0
  96. package/dist/web/assets/index-ZtfxZOyb.js +52 -0
  97. package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +1 -0
  98. package/dist/web/assets/input-D-dR8RI6.js +1 -0
  99. package/dist/web/assets/{jinja-DWU92jHm.js → jinja-Cjw-evAn.js} +1 -1
  100. package/dist/web/assets/{jira-watcher-section-BhQzWJ3r.js → jira-watcher-section-DIC1yIV3.js} +1 -1
  101. package/dist/web/assets/{jison-Bt3f5-S6.js → jison-DfATMYoY.js} +1 -1
  102. package/dist/web/assets/{julia-BS-e0jQg.js → julia-DDCaxiqT.js} +1 -1
  103. package/dist/web/assets/{just-OU89Ot3I.js → just-CJL529u6.js} +1 -1
  104. package/dist/web/assets/keybindings-store-tVfhPANS.js +1 -0
  105. package/dist/web/assets/keyboard-shortcuts-section-BW03P3AF.js +1 -0
  106. package/dist/web/assets/{latex-Bt5mTs31.js → latex-Zc4EpLpW.js} +1 -1
  107. package/dist/web/assets/{liquid-C03IGCJ6.js → liquid-B_6KMNYS.js} +1 -1
  108. package/dist/web/assets/{lua-Dl4zPu0P.js → lua-BtS2idK7.js} +1 -1
  109. package/dist/web/assets/markdown-renderer-CvL1pWeK.js +3 -0
  110. package/dist/web/assets/markdown-renderer-sJNNVhlS.js +1 -0
  111. package/dist/web/assets/{marko-BEnkIoHY.js → marko-Wk6LBnck.js} +1 -1
  112. package/dist/web/assets/{mdc-DGglct19.js → mdc-cRaXBrzL.js} +1 -1
  113. package/dist/web/assets/{media-url-LiPp2UsV.js → media-url-DL8OCGGg.js} +1 -1
  114. package/dist/web/assets/{mobile-bottom-sheet-BizNfPAu.js → mobile-bottom-sheet-CoUauvLF.js} +1 -1
  115. package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +1 -0
  116. package/dist/web/assets/monaco-dotenv-language-BDYnKcnr.js +1 -0
  117. package/dist/web/assets/monaspace-argon-latin-400-italic-DHf095HZ.woff2 +0 -0
  118. package/dist/web/assets/monaspace-argon-latin-400-italic-DZMiIHEH.woff +0 -0
  119. package/dist/web/assets/monaspace-argon-latin-400-normal-DZUN4ACH.woff +0 -0
  120. package/dist/web/assets/monaspace-argon-latin-400-normal-RS-2TGNg.woff2 +0 -0
  121. package/dist/web/assets/monaspace-argon-latin-700-normal-4y--rLW1.woff +0 -0
  122. package/dist/web/assets/monaspace-argon-latin-700-normal-D9kWRRK5.woff2 +0 -0
  123. package/dist/web/assets/monaspace-krypton-latin-400-italic-BoBCEexI.woff2 +0 -0
  124. package/dist/web/assets/monaspace-krypton-latin-400-italic-LWEicbvC.woff +0 -0
  125. package/dist/web/assets/monaspace-krypton-latin-400-normal-D9XlxC9R.woff +0 -0
  126. package/dist/web/assets/monaspace-krypton-latin-400-normal-DsuVXUab.woff2 +0 -0
  127. package/dist/web/assets/nerd-symbols-codicons-DSvMY_FK.woff2 +0 -0
  128. package/dist/web/assets/nerd-symbols-devicons-BBab8P54.woff2 +0 -0
  129. package/dist/web/assets/nerd-symbols-font-awesome-BzNW1pRA.woff2 +0 -0
  130. package/dist/web/assets/nerd-symbols-font-awesome-ext-D30e--UY.woff2 +0 -0
  131. package/dist/web/assets/nerd-symbols-font-awesome-legacy-D_qekXx9.woff2 +0 -0
  132. package/dist/web/assets/nerd-symbols-font-logos-gdu3INfy.woff2 +0 -0
  133. package/dist/web/assets/nerd-symbols-iec-power-Cw6TNeDf.woff2 +0 -0
  134. package/dist/web/assets/nerd-symbols-material-l-CXi9Ay.woff2 +0 -0
  135. package/dist/web/assets/nerd-symbols-misc-BaVfdyeB.woff2 +0 -0
  136. package/dist/web/assets/nerd-symbols-octicons-CJLTTzKm.woff2 +0 -0
  137. package/dist/web/assets/nerd-symbols-pomicons-D7T3Ya3H.woff2 +0 -0
  138. package/dist/web/assets/nerd-symbols-powerline-Cgg0sdGS.woff2 +0 -0
  139. package/dist/web/assets/nerd-symbols-seti-DYiUFq6z.woff2 +0 -0
  140. package/dist/web/assets/nerd-symbols-weather-D4Jrp5XD.woff2 +0 -0
  141. package/dist/web/assets/{nextflow-DKFYidyC.js → nextflow-Bos2wvDV.js} +1 -1
  142. package/dist/web/assets/{nginx-Bhq0tzlh.js → nginx-DMlOwzdr.js} +1 -1
  143. package/dist/web/assets/{nim-CSuBzart.js → nim-D2dZQA88.js} +1 -1
  144. package/dist/web/assets/notification-store-BZbsPnW0.js +1 -0
  145. package/dist/web/assets/notifications-settings-section-DPUROMw-.js +1 -0
  146. package/dist/web/assets/{number-overlay-editor-CYHlP43e.js → number-overlay-editor-DZ2i_Bnb.js} +1 -1
  147. package/dist/web/assets/{overview-panel-xn2BYvn6.js → overview-panel-R146OIom.js} +1 -1
  148. package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +1 -0
  149. package/dist/web/assets/panel-store-BVLBRn6M.js +1 -0
  150. package/dist/web/assets/pdf-preview-BWUPGLp6.js +1 -0
  151. package/dist/web/assets/{perl-uDVR7Ceh.js → perl-C5-Z6fPn.js} +1 -1
  152. package/dist/web/assets/{php-Bby8J3zk.js → php-BXNNhkMH.js} +1 -1
  153. package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +1 -0
  154. package/dist/web/assets/{postgres-viewer-CJbHLcGE.js → postgres-viewer-CjOvaruz.js} +3 -3
  155. package/dist/web/assets/ppmbot-settings-section-DrvWh8l8.js +1 -0
  156. package/dist/web/assets/problems-panel-B7BWVhdy.js +1 -0
  157. package/dist/web/assets/process-table-BRMS1MrX.js +1 -0
  158. package/dist/web/assets/{project-store-Dy3A3LJT.js → project-store-BhMbVln3.js} +1 -1
  159. package/dist/web/assets/proxy-settings-section-Bv0Ny-Yy.js +1 -0
  160. package/dist/web/assets/{pug-EapfojTW.js → pug-C1zf1yWy.js} +1 -1
  161. package/dist/web/assets/{qml-DiR6J5_C.js → qml-OW58jGpD.js} +1 -1
  162. package/dist/web/assets/query-audit-section-BcHJZcVC.js +1 -0
  163. package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +1 -0
  164. package/dist/web/assets/{razor-B2QvlGNY.js → razor-DzjSaobM.js} +1 -1
  165. package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +2 -0
  166. package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +1 -0
  167. package/dist/web/assets/remote-desktop-readiness-gate-BTzss2wX.js +1 -0
  168. package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +1 -0
  169. package/dist/web/assets/{rst-D2eJ0kH4.js → rst-RfuihOUW.js} +1 -1
  170. package/dist/web/assets/{ruby-DzpnBpz-.js → ruby-CNO3pli1.js} +1 -1
  171. package/dist/web/assets/{run-in-terminal-DisO5HC-.js → run-in-terminal-Cb_hVU8x.js} +1 -1
  172. package/dist/web/assets/{sas-WDAKsX0U.js → sas-DZY5BtXx.js} +1 -1
  173. package/dist/web/assets/schedules-settings-section-DO8xAOVj.js +1 -0
  174. package/dist/web/assets/scroll-area-LtNV8g5o.js +1 -0
  175. package/dist/web/assets/{scss-Ci6PdscL.js → scss--4g-REcN.js} +1 -1
  176. package/dist/web/assets/{send-to-chat-DDFdKWNf.js → send-to-chat-Cnpru1M0.js} +1 -1
  177. package/dist/web/assets/{separator-CyisF27O.js → separator-Cds1yxy8.js} +1 -1
  178. package/dist/web/assets/settings-body-Rnu-FcX-.js +2 -0
  179. package/dist/web/assets/settings-store-DZPgPtEA.js +2 -0
  180. package/dist/web/assets/settings-tab-C6PHxs0Z.js +1 -0
  181. package/dist/web/assets/settings-window-content-CQ4gT2qj.js +1 -0
  182. package/dist/web/assets/{shaderlab-Cv3mWofL.js → shaderlab-Cu4sBiVn.js} +1 -1
  183. package/dist/web/assets/{shellsession-D1KVtQa1.js → shellsession-DWzJfEv7.js} +1 -1
  184. package/dist/web/assets/{soy-BGLBwFZT.js → soy-BnqE-5Ff.js} +1 -1
  185. package/dist/web/assets/{sparql-TvnQmFDh.js → sparql-Ctg__NhZ.js} +1 -1
  186. package/dist/web/assets/{sql-query-editor-DaQqRBNA.js → sql-query-editor-D9k19T_M.js} +1 -1
  187. package/dist/web/assets/sqlite-viewer-DoV9hnWJ.js +1 -0
  188. package/dist/web/assets/{stata-Gi-BKyEv.js → stata-Qk2h66XW.js} +1 -1
  189. package/dist/web/assets/{streaming-store-DAbnOfLu.js → streaming-store-uDu3PoN1.js} +1 -1
  190. package/dist/web/assets/{surrealql-BVXs6tkz.js → surrealql-8QEX7EfH.js} +1 -1
  191. package/dist/web/assets/{svelte-BlnFpLWV.js → svelte-D_mubC5-.js} +1 -1
  192. package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +2 -0
  193. package/dist/web/assets/system-monitor-tab-krAcvwF7.js +1 -0
  194. package/dist/web/assets/system-monitor-window-content-qXLcicFM.js +1 -0
  195. package/dist/web/assets/tab-host-window-content-7fPZNEni.js +1 -0
  196. package/dist/web/assets/{tab-store-B7BksLr-.js → tab-store-DGJGnbwv.js} +1 -1
  197. package/dist/web/assets/team-member-sheet-2FnRmk1a.js +2 -0
  198. package/dist/web/assets/team-member-window-content-BgvJFypM.js +1 -0
  199. package/dist/web/assets/{templ-YJYH5r1A.js → templ-C1jV0fsO.js} +1 -1
  200. package/dist/web/assets/terminal-tab-Cs0sGljx.js +3 -0
  201. package/dist/web/assets/{tex-IVZLDIeh.js → tex-D_StFXy_.js} +1 -1
  202. package/dist/web/assets/tool-cards-DU_S2o51.js +9 -0
  203. package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +1 -0
  204. package/dist/web/assets/{ts-tags-DBd321I-.js → ts-tags-B5m47rEm.js} +1 -1
  205. package/dist/web/assets/{twig-DQENQjEn.js → twig-C_br6GIl.js} +1 -1
  206. package/dist/web/assets/{use-blob-url-CGChX4vi.js → use-blob-url-C-SGi_Hb.js} +1 -1
  207. package/dist/web/assets/use-inline-blame-B4trxGcR.js +16 -0
  208. package/dist/web/assets/use-lsp-CV0vzILO.js +6 -0
  209. package/dist/web/assets/use-monaco-theme-DBqbWjWX.js +1 -0
  210. package/dist/web/assets/{use-open-team-member-kmNnlzDD.js → use-open-team-member-DENsjp0s.js} +1 -1
  211. package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +1 -0
  212. package/dist/web/assets/{use-websocket-D8OGBwH8.js → use-websocket-LgXiX1qN.js} +1 -1
  213. package/dist/web/assets/{vendor-mermaid-BoRFiriK.js → vendor-mermaid-Cbig5zA6.js} +1 -1
  214. package/dist/web/assets/video-preview-BDNwumbW.js +1 -0
  215. package/dist/web/assets/{vue-Do2JXEwi.js → vue-DQwCnLdr.js} +1 -1
  216. package/dist/web/assets/{vue-html-Bse0CwSC.js → vue-html-T_EoGpPJ.js} +1 -1
  217. package/dist/web/assets/{vue-vine-C8JSXu5Z.js → vue-vine-DmxLqX50.js} +1 -1
  218. package/dist/web/assets/{window-pip-registry-C2dcY0pE.js → window-pip-registry-3l4hoxuD.js} +1 -1
  219. package/dist/web/assets/{window-store-2z4t5YAt.js → window-store-UC1jJeLM.js} +1 -1
  220. package/dist/web/assets/ws-auth-DEAJiGCR.js +1 -0
  221. package/dist/web/assets/{xml-OX6YmzGK.js → xml-Bs6Bw8is.js} +1 -1
  222. package/dist/web/assets/{xsl-RQUQgcgy.js → xsl-f5ZNhRCc.js} +1 -1
  223. package/dist/web/index.html +105 -71
  224. package/dist/web/sw.js +1 -1
  225. package/docs/project-roadmap.md +2 -0
  226. package/licenses/Apache-2.0.txt +73 -0
  227. package/licenses/CC-BY-4.0.txt +156 -0
  228. package/licenses/MIT.txt +18 -0
  229. package/licenses/OFL-1.1.txt +43 -0
  230. package/licenses/Unlicense.txt +10 -0
  231. package/package.json +14 -7
  232. package/packages/ext-git-graph/src/blame-view.ts +4 -5
  233. package/packages/ext-git-graph/src/commit-detail-escaping.test.ts +205 -0
  234. package/packages/ext-git-graph/src/commit-detail-file-cap.test.ts +65 -0
  235. package/packages/ext-git-graph/src/commit-message-html.test.ts +214 -0
  236. package/packages/ext-git-graph/src/commit-message-html.ts +190 -0
  237. package/packages/ext-git-graph/src/commit-request-generation.test.ts +57 -0
  238. package/packages/ext-git-graph/src/compare-view.ts +4 -5
  239. package/packages/ext-git-graph/src/extension.ts +148 -36
  240. package/packages/ext-git-graph/src/file-history-view.ts +4 -5
  241. package/packages/ext-git-graph/src/log-args.test.ts +57 -0
  242. package/packages/ext-git-graph/src/ppm-api.ts +39 -6
  243. package/packages/ext-git-graph/src/project-scope.test.ts +77 -0
  244. package/packages/ext-git-graph/src/project-scope.ts +69 -0
  245. package/packages/ext-git-graph/src/shortstat-parser.test.ts +53 -0
  246. package/packages/ext-git-graph/src/shortstat-parser.ts +45 -0
  247. package/packages/ext-git-graph/src/types.ts +2 -0
  248. package/packages/ext-git-graph/src/webview-html.test.ts +407 -0
  249. package/packages/ext-git-graph/src/webview-html.ts +631 -108
  250. package/packages/ext-git-graph/src/webview-shell.test.ts +63 -0
  251. package/packages/ext-git-graph/src/webview-shell.ts +48 -7
  252. package/scripts/copy-monaco.ts +63 -0
  253. package/scripts/gen-file-icons.ts +485 -0
  254. package/scripts/gen-nerd-font.ts +311 -0
  255. package/scripts/gen-product-icons.ts +442 -0
  256. package/scripts/monaco-staging.ts +32 -0
  257. package/scripts/precompress-web.ts +77 -0
  258. package/scripts/third-party-notices.ts +160 -0
  259. package/scripts/vite-monaco-dev-assets.ts +63 -0
  260. package/src/providers/claude-agent-sdk.ts +32 -2
  261. package/src/server/index.ts +17 -0
  262. package/src/server/routes/chat.ts +16 -6
  263. package/src/server/routes/git.ts +113 -1
  264. package/src/server/routes/lsp.ts +58 -0
  265. package/src/server/routes/project-scoped.ts +2 -0
  266. package/src/server/routes/static-encoding.ts +73 -0
  267. package/src/server/routes/static-fallback.ts +51 -0
  268. package/src/server/routes/static.ts +41 -5
  269. package/src/server/ws/lsp.ts +433 -0
  270. package/src/services/chat-search.service.ts +211 -26
  271. package/src/services/fs-ops/fs-real-path.ts +23 -0
  272. package/src/services/git-blame/commit-line-diff.ts +124 -0
  273. package/src/services/git-blame/git-blame.service.ts +96 -1
  274. package/src/services/git-repos/git-repo-discovery.ts +152 -0
  275. package/src/services/lsp/lsp-capabilities.ts +160 -0
  276. package/src/services/lsp/lsp-manager.ts +442 -0
  277. package/src/services/lsp/lsp-protocol.ts +170 -0
  278. package/src/services/lsp/lsp-session.ts +466 -0
  279. package/src/services/lsp/server-registry.ts +401 -0
  280. package/src/services/search-index-db.service.ts +10 -2
  281. package/src/shared/blame.ts +51 -0
  282. package/src/shared/lsp-uri.ts +108 -0
  283. package/src/web/app.tsx +6 -0
  284. package/src/web/components/ai-resources/ai-resource-editor.tsx +1 -1
  285. package/src/web/components/ai-resources/ai-resources-panel.tsx +1 -1
  286. package/src/web/components/ai-resources/mcp-group.tsx +1 -1
  287. package/src/web/components/ai-resources/new-resource-menu.tsx +1 -1
  288. package/src/web/components/ai-resources/resource-visuals.tsx +1 -1
  289. package/src/web/components/auth/login-screen.tsx +1 -1
  290. package/src/web/components/auth/support-dialog.tsx +1 -1
  291. package/src/web/components/chat/attachment-chips.tsx +113 -5
  292. package/src/web/components/chat/background-command-bar.tsx +1 -1
  293. package/src/web/components/chat/background-output-panel.tsx +1 -1
  294. package/src/web/components/chat/change-edit-list.tsx +1 -1
  295. package/src/web/components/chat/change-file-row.tsx +1 -1
  296. package/src/web/components/chat/chat-history-bar.tsx +2 -2
  297. package/src/web/components/chat/chat-history-panel.tsx +1 -1
  298. package/src/web/components/chat/chat-placeholder.tsx +1 -1
  299. package/src/web/components/chat/chat-scroll-nav.tsx +1 -1
  300. package/src/web/components/chat/chat-tab.tsx +1 -1
  301. package/src/web/components/chat/chat-toolbar-overflow-menu.tsx +1 -1
  302. package/src/web/components/chat/chat-welcome.tsx +1 -1
  303. package/src/web/components/chat/codex-usage-panel.tsx +1 -1
  304. package/src/web/components/chat/file-picker.tsx +1 -1
  305. package/src/web/components/chat/image-preview-states.tsx +1 -1
  306. package/src/web/components/chat/message-action-bar.tsx +1 -1
  307. package/src/web/components/chat/message-input.tsx +1 -1
  308. package/src/web/components/chat/message-list.tsx +1 -1
  309. package/src/web/components/chat/mode-selector.tsx +1 -1
  310. package/src/web/components/chat/model-selector.tsx +1 -1
  311. package/src/web/components/chat/model-thinking-selector.tsx +1 -1
  312. package/src/web/components/chat/pre-compact-button.tsx +1 -1
  313. package/src/web/components/chat/provider-selector.tsx +1 -1
  314. package/src/web/components/chat/send-message-card.tsx +1 -1
  315. package/src/web/components/chat/session-context-menu.tsx +1 -1
  316. package/src/web/components/chat/session-debug-dialog.tsx +1 -1
  317. package/src/web/components/chat/session-history-list.tsx +1 -1
  318. package/src/web/components/chat/session-list-panel.tsx +1 -1
  319. package/src/web/components/chat/session-picker.tsx +1 -1
  320. package/src/web/components/chat/slash-command-picker.tsx +1 -1
  321. package/src/web/components/chat/task-tracker.tsx +1 -1
  322. package/src/web/components/chat/team-activity-panel.tsx +1 -1
  323. package/src/web/components/chat/team-member-activity-row.tsx +1 -1
  324. package/src/web/components/chat/team-member-list.tsx +1 -1
  325. package/src/web/components/chat/team-member-sheet.tsx +1 -1
  326. package/src/web/components/chat/team-member-window-content.tsx +1 -1
  327. package/src/web/components/chat/team-working-bar.tsx +1 -1
  328. package/src/web/components/chat/tool-cards.tsx +2 -2
  329. package/src/web/components/chat/turn-change-sheet.tsx +1 -1
  330. package/src/web/components/chat/turn-change-tray.tsx +1 -1
  331. package/src/web/components/chat/turn-cost-warning.tsx +1 -1
  332. package/src/web/components/chat/usage-badge.tsx +1 -1
  333. package/src/web/components/chat/usage-panel-shell.tsx +1 -1
  334. package/src/web/components/chat/version-switcher.tsx +1 -1
  335. package/src/web/components/database/connection-import-export.tsx +1 -1
  336. package/src/web/components/database/connection-list.tsx +1 -1
  337. package/src/web/components/database/connection-row.tsx +1 -1
  338. package/src/web/components/database/database-sidebar.tsx +1 -1
  339. package/src/web/components/database/database-viewer.tsx +1 -1
  340. package/src/web/components/database/export-button.tsx +1 -1
  341. package/src/web/components/database/glide-context-menu.tsx +1 -1
  342. package/src/web/components/database/glide-data-grid.tsx +1 -1
  343. package/src/web/components/database/glide-data-preview-panel.tsx +2 -2
  344. package/src/web/components/database/glide-grid-pagination.tsx +1 -1
  345. package/src/web/components/database/glide-grid-toolbar.tsx +1 -1
  346. package/src/web/components/database/glide-header-menu.tsx +1 -1
  347. package/src/web/components/database/glide-save-bar.tsx +1 -1
  348. package/src/web/components/database/schema-table-tree.tsx +1 -1
  349. package/src/web/components/editor/audio-preview.tsx +1 -1
  350. package/src/web/components/editor/code-editor.tsx +167 -21
  351. package/src/web/components/editor/compare-picker.tsx +1 -1
  352. package/src/web/components/editor/conflict-editor.tsx +15 -6
  353. package/src/web/components/editor/csv-preview.tsx +1 -1
  354. package/src/web/components/editor/diff-viewer.tsx +59 -37
  355. package/src/web/components/editor/docx-preview.tsx +1 -1
  356. package/src/web/components/editor/editor-breadcrumb-external.tsx +1 -1
  357. package/src/web/components/editor/editor-breadcrumb.tsx +1 -1
  358. package/src/web/components/editor/editor-language-picker.tsx +3 -1
  359. package/src/web/components/editor/editor-language-service.tsx +77 -0
  360. package/src/web/components/editor/editor-mobile-toolbar.tsx +13 -2
  361. package/src/web/components/editor/editor-placeholder.tsx +1 -1
  362. package/src/web/components/editor/editor-toolbar.tsx +1 -1
  363. package/src/web/components/editor/image-preview.tsx +1 -1
  364. package/src/web/components/editor/lsp-status.tsx +241 -0
  365. package/src/web/components/editor/pdf-preview.tsx +1 -1
  366. package/src/web/components/editor/video-player/video-player-controls.tsx +1 -1
  367. package/src/web/components/editor/video-player/video-player.tsx +1 -1
  368. package/src/web/components/editor/video-player/video-settings-menu.tsx +1 -1
  369. package/src/web/components/editor/video-preview.tsx +1 -1
  370. package/src/web/components/explorer/file-tree.tsx +1 -1
  371. package/src/web/components/explorer/inline-tree-input.tsx +1 -1
  372. package/src/web/components/explorer/search-panel.tsx +3 -2
  373. package/src/web/components/explorer/tree-node-context-menu.tsx +1 -1
  374. package/src/web/components/explorer/tree-node.tsx +5 -12
  375. package/src/web/components/extensions/extension-quickpick.tsx +1 -1
  376. package/src/web/components/extensions/extension-tree-view.tsx +1 -1
  377. package/src/web/components/extensions/extension-webview.tsx +53 -20
  378. package/src/web/components/extensions/webview-theme.ts +121 -0
  379. package/src/web/components/floating-window/pip/pip-caption-button.tsx +1 -1
  380. package/src/web/components/git/create-worktree-dialog.tsx +5 -3
  381. package/src/web/components/git/git-log-panel.tsx +4 -2
  382. package/src/web/components/git/git-placeholder.tsx +1 -1
  383. package/src/web/components/git/git-ref-badge.tsx +1 -1
  384. package/src/web/components/git/git-repo-picker.tsx +145 -0
  385. package/src/web/components/git/git-status-panel.tsx +231 -250
  386. package/src/web/components/git/git-worktree-panel.tsx +6 -4
  387. package/src/web/components/git/hunk-stage-dialog.tsx +5 -3
  388. package/src/web/components/group-chat/group-chat-tab.tsx +1 -1
  389. package/src/web/components/group-chat/group-create-dialog.tsx +1 -1
  390. package/src/web/components/group-chat/group-full-transcript-view.tsx +1 -1
  391. package/src/web/components/group-chat/group-list.tsx +1 -1
  392. package/src/web/components/group-chat/group-member-edit-dialog.tsx +1 -1
  393. package/src/web/components/group-chat/group-member-roster.tsx +1 -1
  394. package/src/web/components/group-chat/group-message-item.tsx +1 -1
  395. package/src/web/components/group-chat/group-transcript-messages.tsx +1 -1
  396. package/src/web/components/jira/jira-config-form.tsx +1 -1
  397. package/src/web/components/jira/jira-filter-builder.tsx +1 -1
  398. package/src/web/components/jira/jira-panel.tsx +1 -1
  399. package/src/web/components/jira/jira-results-panel.tsx +1 -1
  400. package/src/web/components/jira/jira-ticket-card.tsx +1 -1
  401. package/src/web/components/jira/jira-ticket-detail.tsx +1 -1
  402. package/src/web/components/jira/jira-watcher-form.tsx +1 -1
  403. package/src/web/components/jira/jira-watcher-list.tsx +1 -1
  404. package/src/web/components/layout/add-project-form.tsx +1 -1
  405. package/src/web/components/layout/cloud-alias-row.tsx +1 -1
  406. package/src/web/components/layout/cloud-share-named-tunnel-row.tsx +1 -1
  407. package/src/web/components/layout/cloud-share-popover.tsx +1 -1
  408. package/src/web/components/layout/command-palette-filter-chips.tsx +1 -1
  409. package/src/web/components/layout/command-palette.tsx +46 -8
  410. package/src/web/components/layout/dock-header.tsx +4 -4
  411. package/src/web/components/layout/dock-panel.tsx +3 -3
  412. package/src/web/components/layout/draggable-tab.tsx +20 -3
  413. package/src/web/components/layout/editor-panel.tsx +1 -1
  414. package/src/web/components/layout/mobile-drawer-tab-bar.tsx +10 -1
  415. package/src/web/components/layout/mobile-drawer.tsx +1 -1
  416. package/src/web/components/layout/mobile-nav.tsx +3 -3
  417. package/src/web/components/layout/mobile-tab-switcher-sheet.tsx +13 -4
  418. package/src/web/components/layout/nav-section-rail.tsx +1 -1
  419. package/src/web/components/layout/notification-bell-popover.tsx +1 -1
  420. package/src/web/components/layout/panel-layout.tsx +1 -1
  421. package/src/web/components/layout/project-bottom-sheet.tsx +4 -1
  422. package/src/web/components/layout/project-sort.ts +1 -1
  423. package/src/web/components/layout/project-switcher.tsx +1 -1
  424. package/src/web/components/layout/reparenting-tab.tsx +1 -1
  425. package/src/web/components/layout/sidebar.tsx +1 -1
  426. package/src/web/components/layout/status-bar.tsx +4 -1
  427. package/src/web/components/layout/tab-bar.tsx +9 -23
  428. package/src/web/components/layout/tab-content.tsx +6 -1
  429. package/src/web/components/layout/tab-pool.tsx +1 -0
  430. package/src/web/components/layout/tab-pop-out-menu-item.tsx +1 -1
  431. package/src/web/components/layout/upgrade-button.tsx +1 -1
  432. package/src/web/components/layout/wake-lock-indicator.tsx +1 -1
  433. package/src/web/components/os-explorer/explorer-body.tsx +1 -1
  434. package/src/web/components/os-explorer/explorer-breadcrumb.tsx +1 -1
  435. package/src/web/components/os-explorer/explorer-sidebar.tsx +1 -1
  436. package/src/web/components/os-explorer/explorer-sort-menu.tsx +1 -1
  437. package/src/web/components/os-explorer/explorer-toolbar.tsx +1 -1
  438. package/src/web/components/os-explorer/icons/file-type-icon-map.ts +10 -94
  439. package/src/web/components/os-explorer/icons/file-type-icon.tsx +17 -13
  440. package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +1 -1
  441. package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +1 -1
  442. package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +1 -1
  443. package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +1 -1
  444. package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +1 -1
  445. package/src/web/components/os-explorer/properties-dialog.tsx +1 -1
  446. package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +1 -1
  447. package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +1 -1
  448. package/src/web/components/os-explorer/upload/upload-batch-card.tsx +1 -1
  449. package/src/web/components/os-explorer/upload/upload-batch-row.tsx +1 -1
  450. package/src/web/components/os-explorer/use-coarse-long-press.ts +8 -0
  451. package/src/web/components/os-explorer/views/column-view-mobile.tsx +1 -1
  452. package/src/web/components/os-explorer/views/icons-view-tile.tsx +1 -1
  453. package/src/web/components/os-explorer/views/list-row.tsx +1 -1
  454. package/src/web/components/os-explorer/views/list-view.tsx +1 -1
  455. package/src/web/components/postgres/postgres-viewer.tsx +1 -1
  456. package/src/web/components/problems/problems-panel.tsx +230 -0
  457. package/src/web/components/problems/problems-status.tsx +53 -0
  458. package/src/web/components/projects/dir-suggest.tsx +1 -1
  459. package/src/web/components/projects/project-list.tsx +1 -1
  460. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +1 -1
  461. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +1 -1
  462. package/src/web/components/remote-desktop/remote-desktop-readiness-gate.tsx +1 -1
  463. package/src/web/components/remote-desktop/remote-desktop-warning-gate.tsx +1 -1
  464. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +1 -1
  465. package/src/web/components/root-error-boundary.tsx +116 -0
  466. package/src/web/components/settings/accounts/account-add-dialog.tsx +1 -1
  467. package/src/web/components/settings/accounts/account-card.tsx +1 -1
  468. package/src/web/components/settings/accounts/account-export-dialog.tsx +1 -1
  469. package/src/web/components/settings/accounts/account-export-simulation.tsx +1 -1
  470. package/src/web/components/settings/accounts/account-import-dialog.tsx +1 -1
  471. package/src/web/components/settings/accounts/account-profile-panel.tsx +1 -1
  472. package/src/web/components/settings/accounts/account-rotation-settings.tsx +1 -1
  473. package/src/web/components/settings/accounts/account-token-test-dialog.tsx +1 -1
  474. package/src/web/components/settings/accounts/account-usage-pattern-chart.tsx +1 -1
  475. package/src/web/components/settings/accounts/accounts-pane-header.tsx +1 -1
  476. package/src/web/components/settings/accounts/accounts-settings-section.tsx +1 -1
  477. package/src/web/components/settings/accounts/claude-accounts-section.tsx +9 -1
  478. package/src/web/components/settings/accounts/codex-accounts-section.tsx +10 -1
  479. package/src/web/components/settings/accounts/codex-add-account-dialog.tsx +1 -1
  480. package/src/web/components/settings/accounts/codex-backup-dialog.tsx +1 -1
  481. package/src/web/components/settings/ai-settings-section.tsx +1 -1
  482. package/src/web/components/settings/appearance-settings-section.tsx +56 -1
  483. package/src/web/components/settings/change-password-section.tsx +1 -1
  484. package/src/web/components/settings/extension-manager-section.tsx +1 -1
  485. package/src/web/components/settings/general-settings-section.tsx +1 -1
  486. package/src/web/components/settings/glob-list-editor.tsx +1 -1
  487. package/src/web/components/settings/keyboard-shortcuts-section.tsx +1 -1
  488. package/src/web/components/settings/mcp-server-dialog.tsx +1 -1
  489. package/src/web/components/settings/notifications-settings-section.tsx +1 -1
  490. package/src/web/components/settings/ppmbot-settings-section.tsx +1 -1
  491. package/src/web/components/settings/proxy-settings-section.tsx +1 -1
  492. package/src/web/components/settings/proxy-test-section.tsx +1 -1
  493. package/src/web/components/settings/query-audit-section.tsx +1 -1
  494. package/src/web/components/settings/schedules/schedule-row.tsx +1 -1
  495. package/src/web/components/settings/schedules/schedules-settings-section.tsx +1 -1
  496. package/src/web/components/settings/settings-body.tsx +1 -1
  497. package/src/web/components/settings/settings-categories.ts +14 -3
  498. package/src/web/components/settings/settings-category-list.tsx +1 -1
  499. package/src/web/components/settings/settings-section-content.tsx +1 -1
  500. package/src/web/components/settings/tag-settings-section.tsx +1 -1
  501. package/src/web/components/settings/theme-grid.tsx +1 -1
  502. package/src/web/components/settings/theme-manager-section.tsx +1 -1
  503. package/src/web/components/settings/theme-picker.tsx +1 -1
  504. package/src/web/components/shared/bug-report-popup.tsx +1 -1
  505. package/src/web/components/shared/connection-lost-overlay.tsx +1 -1
  506. package/src/web/components/shared/diagram-overlay.tsx +1 -1
  507. package/src/web/components/shared/image-overlay.tsx +1 -1
  508. package/src/web/components/shared/theme-mode-menu.tsx +1 -1
  509. package/src/web/components/sqlite/sqlite-data-grid.tsx +1 -1
  510. package/src/web/components/sqlite/sqlite-table-list.tsx +1 -1
  511. package/src/web/components/sqlite/sqlite-viewer.tsx +1 -1
  512. package/src/web/components/system/process-group-row.tsx +1 -1
  513. package/src/web/components/system/process-row.tsx +1 -1
  514. package/src/web/components/system/process-table-toolbar.tsx +1 -1
  515. package/src/web/components/system/resource-status-bar.tsx +1 -1
  516. package/src/web/components/system/sortable-header.tsx +1 -1
  517. package/src/web/components/system/system-monitor-body.tsx +1 -1
  518. package/src/web/components/terminal/terminal-finish-login.tsx +1 -1
  519. package/src/web/components/terminal/terminal-links-sheet.tsx +1 -1
  520. package/src/web/components/terminal/terminal-mobile-toolbar.tsx +1 -1
  521. package/src/web/components/terminal/terminal-placeholder.tsx +1 -1
  522. package/src/web/components/terminal/terminal-tab.tsx +1 -1
  523. package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +1 -0
  524. package/src/web/components/tunnels/named-tunnel/named-tunnel-login-step.tsx +1 -1
  525. package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +1 -1
  526. package/src/web/components/tunnels/named-tunnel/named-tunnel-setup-content.tsx +9 -3
  527. package/src/web/components/tunnels/named-tunnel/named-tunnel-setup-popup.tsx +1 -1
  528. package/src/web/components/tunnels/named-tunnel/named-tunnel-step-reducer.ts +14 -2
  529. package/src/web/components/tunnels/tunnel-manager-tab.tsx +1 -1
  530. package/src/web/components/ui/accordion.tsx +1 -1
  531. package/src/web/components/ui/adaptive-context-menu.tsx +9 -2
  532. package/src/web/components/ui/browse-button.tsx +1 -1
  533. package/src/web/components/ui/context-menu.tsx +1 -1
  534. package/src/web/components/ui/dialog.tsx +1 -1
  535. package/src/web/components/ui/dropdown-menu.tsx +1 -1
  536. package/src/web/components/ui/file-browser-picker.tsx +4 -6
  537. package/src/web/components/ui/select.tsx +1 -1
  538. package/src/web/components/ui/sidebar-header.tsx +1 -1
  539. package/src/web/components/ui/sonner.tsx +1 -1
  540. package/src/web/hooks/use-git-repo.ts +124 -0
  541. package/src/web/hooks/use-global-keybindings.ts +11 -6
  542. package/src/web/hooks/use-inline-blame.ts +161 -11
  543. package/src/web/hooks/use-is-touch-only.ts +46 -0
  544. package/src/web/hooks/use-lsp.ts +182 -0
  545. package/src/web/hooks/use-terminal.ts +60 -2
  546. package/src/web/hooks/use-touch-tab-drag.ts +18 -2
  547. package/src/web/hooks/use-url-sync.ts +15 -1
  548. package/src/web/index.html +74 -0
  549. package/src/web/lib/blame-hover-commands.ts +82 -0
  550. package/src/web/lib/blame-hover.ts +199 -0
  551. package/src/web/lib/chunk-recovery.ts +113 -0
  552. package/src/web/lib/editor-font.ts +70 -0
  553. package/src/web/lib/ext-command-dispatch.ts +24 -0
  554. package/src/web/lib/file-icons.generated.ts +408 -0
  555. package/src/web/lib/file-icons.tsx +192 -0
  556. package/src/web/lib/fluent-icon.tsx +62 -0
  557. package/src/web/lib/git-file-tree.ts +83 -0
  558. package/src/web/lib/git-repo-scope.ts +157 -0
  559. package/src/web/lib/icons.generated.tsx +428 -0
  560. package/src/web/lib/icons.ts +17 -0
  561. package/src/web/lib/lsp/lsp-client.ts +321 -0
  562. package/src/web/lib/lsp/lsp-documents.ts +39 -0
  563. package/src/web/lib/lsp/lsp-monaco.ts +214 -0
  564. package/src/web/lib/lsp/lsp-navigation.ts +129 -0
  565. package/src/web/lib/lsp/lsp-responses.ts +244 -0
  566. package/src/web/lib/lsp/lsp-semantic-tokens.ts +180 -0
  567. package/src/web/lib/lsp/lsp-shadow-models.ts +254 -0
  568. package/src/web/lib/lsp/monaco-builtin-typescript.ts +149 -0
  569. package/src/web/lib/lsp/register-providers.ts +458 -0
  570. package/src/web/lib/monaco-diff-language.ts +62 -0
  571. package/src/web/lib/monaco-dotenv-language.ts +134 -0
  572. package/src/web/lib/sidebar-tabs/tab-registry.ts +11 -2
  573. package/src/web/lib/tab-type-icons.ts +32 -4
  574. package/src/web/main.tsx +30 -1
  575. package/src/web/stores/git-repo-store.ts +117 -0
  576. package/src/web/stores/git-status-store.ts +10 -7
  577. package/src/web/stores/keybindings-store.ts +1 -0
  578. package/src/web/stores/panel-utils.ts +5 -2
  579. package/src/web/stores/problems-store.ts +116 -0
  580. package/src/web/stores/settings-store.ts +40 -0
  581. package/src/web/stores/tab-store.ts +2 -1
  582. package/src/web/stores/window-panel-persistence.ts +1 -0
  583. package/src/web/styles/file-icons.generated.css +1007 -0
  584. package/src/web/styles/fonts/nerd-symbols-codicons.woff2 +0 -0
  585. package/src/web/styles/fonts/nerd-symbols-devicons.woff2 +0 -0
  586. package/src/web/styles/fonts/nerd-symbols-font-awesome-ext.woff2 +0 -0
  587. package/src/web/styles/fonts/nerd-symbols-font-awesome-legacy.woff2 +0 -0
  588. package/src/web/styles/fonts/nerd-symbols-font-awesome.woff2 +0 -0
  589. package/src/web/styles/fonts/nerd-symbols-font-logos.woff2 +0 -0
  590. package/src/web/styles/fonts/nerd-symbols-iec-power.woff2 +0 -0
  591. package/src/web/styles/fonts/nerd-symbols-material.woff2 +0 -0
  592. package/src/web/styles/fonts/nerd-symbols-misc.woff2 +0 -0
  593. package/src/web/styles/fonts/nerd-symbols-octicons.woff2 +0 -0
  594. package/src/web/styles/fonts/nerd-symbols-pomicons.woff2 +0 -0
  595. package/src/web/styles/fonts/nerd-symbols-powerline.woff2 +0 -0
  596. package/src/web/styles/fonts/nerd-symbols-seti.woff2 +0 -0
  597. package/src/web/styles/fonts/nerd-symbols-weather.woff2 +0 -0
  598. package/src/web/styles/globals.css +30 -3
  599. package/src/web/styles/nerd-font.generated.css +153 -0
  600. package/src/web/sw.ts +69 -2
  601. package/src/web/theme/adapters/monaco-adapter.ts +20 -1
  602. package/src/web/theme/diff-token-rules.ts +34 -0
  603. package/src/web/theme/semantic-token-rules.ts +103 -0
  604. package/src/web/theme/theme-mode-options.ts +1 -1
  605. package/vite.config.ts +24 -7
  606. package/dist/web/assets/accounts-settings-section-ByM99C7p.js +0 -1
  607. package/dist/web/assets/ai-resource-editor-BsB9wa0z.js +0 -1
  608. package/dist/web/assets/ai-settings-section-BT-QVUBa.js +0 -1
  609. package/dist/web/assets/ai-settings-section-BUPOINaw.js +0 -1
  610. package/dist/web/assets/api-client-C6VTn7cG.js +0 -1
  611. package/dist/web/assets/appearance-settings-section-DJEoECoJ.js +0 -1
  612. package/dist/web/assets/architecture-PBZL5I3N-D55g5j-p.js +0 -1
  613. package/dist/web/assets/arrow-left-DpXaakMC.js +0 -1
  614. package/dist/web/assets/arrow-right-BqNSIscF.js +0 -1
  615. package/dist/web/assets/audio-preview-CF4tTFv_.js +0 -1
  616. package/dist/web/assets/bot-D3-n2i31.js +0 -1
  617. package/dist/web/assets/brain-CuAQMSBE.js +0 -1
  618. package/dist/web/assets/calendar-clock-CDP7AVjA.js +0 -1
  619. package/dist/web/assets/chat-tab-fD2vWwj2.js +0 -15
  620. package/dist/web/assets/check-BQKbhtoA.js +0 -1
  621. package/dist/web/assets/chevron-down-B9puixxZ.js +0 -1
  622. package/dist/web/assets/chevron-right-DK_HbYZC.js +0 -1
  623. package/dist/web/assets/chevron-up-BPAINgoK.js +0 -1
  624. package/dist/web/assets/circle-BpGTLAas.js +0 -1
  625. package/dist/web/assets/circle-question-mark-DUaySkoB.js +0 -1
  626. package/dist/web/assets/clock-Dqq0H9yH.js +0 -1
  627. package/dist/web/assets/code-CX6640Tv.js +0 -1
  628. package/dist/web/assets/code-editor-CyFLXCsD.js +0 -7
  629. package/dist/web/assets/codex-daily-guard-Cg19EWxk.js +0 -1
  630. package/dist/web/assets/conflict-editor-DSjo2FRy.js +0 -19
  631. package/dist/web/assets/context-menu-DchAkdnL.js +0 -1
  632. package/dist/web/assets/copy-Bt_1tjoJ.js +0 -1
  633. package/dist/web/assets/csv-preview-BIE3Wpww.js +0 -2
  634. package/dist/web/assets/database-9_p74TtY.js +0 -1
  635. package/dist/web/assets/database-viewer-CMvIORcM.js +0 -1
  636. package/dist/web/assets/diff-viewer-BQQ6fUr6.js +0 -4
  637. package/dist/web/assets/docx-preview-DcCh59Oe.js +0 -12
  638. package/dist/web/assets/download-CEPFebri.js +0 -1
  639. package/dist/web/assets/explorer-body-DRo2JHP9.js +0 -2
  640. package/dist/web/assets/explorer-window-content-7mnRpSqn.js +0 -1
  641. package/dist/web/assets/extension-manager-section-CEfr7-Kv.js +0 -1
  642. package/dist/web/assets/extension-webview-D7HqqdTv.js +0 -3
  643. package/dist/web/assets/eye-BQaWPI4P.js +0 -1
  644. package/dist/web/assets/eye-off-BlB3QV90.js +0 -1
  645. package/dist/web/assets/file-exclamation-point-BDmSZbxs.js +0 -1
  646. package/dist/web/assets/files-settings-section-h6a-WpSO.js +0 -1
  647. package/dist/web/assets/flask-conical-Cj-J6cxs.js +0 -1
  648. package/dist/web/assets/folder-symlink-BSYalAHC.js +0 -1
  649. package/dist/web/assets/general-settings-section-Bt3KNcyM.js +0 -1
  650. package/dist/web/assets/git-log-panel-H-Bg9p7m.js +0 -1
  651. package/dist/web/assets/gitGraph-HDMCJU4V-At-P8hlA.js +0 -1
  652. package/dist/web/assets/glide-data-grid-D3Xc_WGh.js +0 -141
  653. package/dist/web/assets/group-chat-tab-BkLt7J_s.js +0 -1
  654. package/dist/web/assets/hand-BkDyLfcc.js +0 -1
  655. package/dist/web/assets/image-preview-D_6-FhZP.js +0 -1
  656. package/dist/web/assets/index-BV_sFdHY.js +0 -52
  657. package/dist/web/assets/index-Di9nVG3y.css +0 -2
  658. package/dist/web/assets/info-3K5VOQVL-btSsuixb.js +0 -1
  659. package/dist/web/assets/key-round-Cv4z7_KG.js +0 -1
  660. package/dist/web/assets/keybindings-store-alreySno.js +0 -1
  661. package/dist/web/assets/keyboard-HG0BUtuN.js +0 -1
  662. package/dist/web/assets/keyboard-shortcuts-section-B_D0FamP.js +0 -1
  663. package/dist/web/assets/loader-circle-H81ywCuQ.js +0 -1
  664. package/dist/web/assets/markdown-renderer-CG_nGh7k.js +0 -3
  665. package/dist/web/assets/markdown-renderer-CgbqvSuc.js +0 -1
  666. package/dist/web/assets/mobile-explorer-sheet-DAZTzi1k.js +0 -1
  667. package/dist/web/assets/monitor-CKcYUoAV.js +0 -1
  668. package/dist/web/assets/notification-store-HCg5Shw9.js +0 -1
  669. package/dist/web/assets/notifications-settings-section-BAs_p_ai.js +0 -1
  670. package/dist/web/assets/packet-RMMSAZCW-B20_sIFd.js +0 -1
  671. package/dist/web/assets/panel-store-XhAruR6D.js +0 -1
  672. package/dist/web/assets/pdf-preview-BJjqU3AX.js +0 -1
  673. package/dist/web/assets/pie-UPGHQEXC-nzfJdXCC.js +0 -1
  674. package/dist/web/assets/power-D_wlLkC0.js +0 -1
  675. package/dist/web/assets/ppmbot-settings-section-5U0a58oj.js +0 -1
  676. package/dist/web/assets/process-table-CqGL0Bcj.js +0 -1
  677. package/dist/web/assets/proxy-settings-section-B1LDpGSc.js +0 -1
  678. package/dist/web/assets/query-audit-section-DfCvY94_.js +0 -1
  679. package/dist/web/assets/radar-KQ55EAFF-CS4sHKD5.js +0 -1
  680. package/dist/web/assets/remote-desktop-mobile-sheet-_LH4uCcA.js +0 -2
  681. package/dist/web/assets/remote-desktop-mobile-view-C5TUPDh_.js +0 -1
  682. package/dist/web/assets/remote-desktop-readiness-gate-DuT4A893.js +0 -1
  683. package/dist/web/assets/remote-desktop-window-content-B4V-1aJn.js +0 -1
  684. package/dist/web/assets/save-BehCuMf4.js +0 -1
  685. package/dist/web/assets/schedules-settings-section-C2SjwWFB.js +0 -1
  686. package/dist/web/assets/send-CtXcGcsk.js +0 -1
  687. package/dist/web/assets/settings-body-CdFauCAU.js +0 -2
  688. package/dist/web/assets/settings-store-BkNrLdjk.js +0 -2
  689. package/dist/web/assets/settings-tab-DGUlmMbc.js +0 -1
  690. package/dist/web/assets/settings-window-content-CIH5sihe.js +0 -1
  691. package/dist/web/assets/shield-check-z9PX8HVZ.js +0 -1
  692. package/dist/web/assets/shield-off-Du0U5dVI.js +0 -1
  693. package/dist/web/assets/sparkles-CO9zx1f-.js +0 -1
  694. package/dist/web/assets/sqlite-viewer-Bpz7QnBk.js +0 -1
  695. package/dist/web/assets/system-monitor-body-BBJOWFCa.js +0 -2
  696. package/dist/web/assets/system-monitor-tab-D7RIwhmg.js +0 -1
  697. package/dist/web/assets/system-monitor-window-content-CH0UmMP9.js +0 -1
  698. package/dist/web/assets/tab-host-window-content-C_yU5MH_.js +0 -1
  699. package/dist/web/assets/table-BK4h8E7u.js +0 -1
  700. package/dist/web/assets/team-member-sheet-CopMME1E.js +0 -2
  701. package/dist/web/assets/team-member-window-content-CUDQ6oX7.js +0 -1
  702. package/dist/web/assets/terminal-tab-BjI95uG5.js +0 -3
  703. package/dist/web/assets/text-wrap-DLtVgSnU.js +0 -1
  704. package/dist/web/assets/tool-cards-I71v1w5Z.js +0 -9
  705. package/dist/web/assets/treemap-KZPCXAKY-BBrhTPZk.js +0 -1
  706. package/dist/web/assets/use-coarse-long-press-XrJ4Upuv.js +0 -1
  707. package/dist/web/assets/use-inline-blame-CRUwd6FF.js +0 -8
  708. package/dist/web/assets/use-monaco-theme-qzMbRfCW.js +0 -1
  709. package/dist/web/assets/use-remote-desktop-display-choice-0zDkCmsK.js +0 -1
  710. package/dist/web/assets/video-preview-Mlgl7hGr.js +0 -1
  711. package/dist/web/assets/ws-auth-DI1E6Fe7.js +0 -1
  712. package/dist/web/assets/x-DlnNFF2t.js +0 -1
  713. package/dist/web/monacoeditorwork/css.worker.bundle.js +0 -54268
  714. package/dist/web/monacoeditorwork/editor.worker.bundle.js +0 -14316
  715. package/dist/web/monacoeditorwork/html.worker.bundle.js +0 -30452
  716. package/dist/web/monacoeditorwork/json.worker.bundle.js +0 -22095
  717. package/dist/web/monacoeditorwork/ts.worker.bundle.js +0 -225957
  718. package/src/web/components/explorer/file-icon-map.ts +0 -69
  719. /package/dist/web/assets/{abap-BIHt2d9F.js → abap-tkptwpsK.js} +0 -0
  720. /package/dist/web/assets/{actionscript-3-rbCNRHFj.js → actionscript-3-Di9TMGwg.js} +0 -0
  721. /package/dist/web/assets/{ada-CN94LQTJ.js → ada-CB9F079D.js} +0 -0
  722. /package/dist/web/assets/{andromeeda-BsmcoXuQ.js → andromeeda-Dcl8jpb7.js} +0 -0
  723. /package/dist/web/assets/{apache-pRQmqcVZ.js → apache-D0Nk0qsH.js} +0 -0
  724. /package/dist/web/assets/{apex-CNEumC1X.js → apex-mQIoWncD.js} +0 -0
  725. /package/dist/web/assets/{api-client-DKvkxuDr.js → api-client-bfB7cXRM.js} +0 -0
  726. /package/dist/web/assets/{applescript-DjEVsS-3.js → applescript-C6ljbcsI.js} +0 -0
  727. /package/dist/web/assets/{apply-theme-Abd1TNqE.js → apply-theme-BwIMNXyX.js} +0 -0
  728. /package/dist/web/assets/{ara-DbhZRlTL.js → ara-WFqRPGPe.js} +0 -0
  729. /package/dist/web/assets/{asciidoc-BsfbAXIK.js → asciidoc-bNd8w4nf.js} +0 -0
  730. /package/dist/web/assets/{asm-Vw5NMXjI.js → asm-BQ1_F580.js} +0 -0
  731. /package/dist/web/assets/{aurora-x-CoahU6sR.js → aurora-x-Ciqs_Dbu.js} +0 -0
  732. /package/dist/web/assets/{awk-ChKjCxHz.js → awk-GCR-6KZV.js} +0 -0
  733. /package/dist/web/assets/{ayu-dark-By9RKxTf.js → ayu-dark-DxJXuj99.js} +0 -0
  734. /package/dist/web/assets/{ayu-light-CEsgeLON.js → ayu-light-jvSyJWDV.js} +0 -0
  735. /package/dist/web/assets/{ayu-mirage-CYrtwSLW.js → ayu-mirage-Cg3BPO6H.js} +0 -0
  736. /package/dist/web/assets/{ballerina-CCSVz22W.js → ballerina-BPz4CrX6.js} +0 -0
  737. /package/dist/web/assets/{bat-DU4--CMj.js → bat-CSTs2hIG.js} +0 -0
  738. /package/dist/web/assets/{beancount-DF_tlRlF.js → beancount-DMateAZX.js} +0 -0
  739. /package/dist/web/assets/{berry-CZTnatvO.js → berry-9lXg0GRM.js} +0 -0
  740. /package/dist/web/assets/{bibtex-BSpXy8jk.js → bibtex-DIbqmvFg.js} +0 -0
  741. /package/dist/web/assets/{bicep-yv1Ydq5Z.js → bicep-02WGsoJ8.js} +0 -0
  742. /package/dist/web/assets/{bird2-Db7Z7SMN.js → bird2-BY0tbu3k.js} +0 -0
  743. /package/dist/web/assets/{c-C3ivhwA4.js → c-jMBX_9Je.js} +0 -0
  744. /package/dist/web/assets/{c3-N4mKyst9.js → c3-B8JVbNjW.js} +0 -0
  745. /package/dist/web/assets/{cadence-BYK8tFFk.js → cadence-DUIf1tvL.js} +0 -0
  746. /package/dist/web/assets/{can-open-in-ppm-DU5MH6XF.js → can-open-in-ppm-DGEc0s4P.js} +0 -0
  747. /package/dist/web/assets/{catppuccin-frappe-BK9Tv2XG.js → catppuccin-frappe-FhUCVxex.js} +0 -0
  748. /package/dist/web/assets/{catppuccin-latte-De4mVCp3.js → catppuccin-latte-CzfrtmgL.js} +0 -0
  749. /package/dist/web/assets/{catppuccin-macchiato-Bxt_D7cs.js → catppuccin-macchiato-6xL0lOtj.js} +0 -0
  750. /package/dist/web/assets/{catppuccin-mocha-Dz2o8gN7.js → catppuccin-mocha-D39-xS3P.js} +0 -0
  751. /package/dist/web/assets/{clarity-BJdh0Pw-.js → clarity-D8TeGvoT.js} +0 -0
  752. /package/dist/web/assets/{clojure-DG-FYGzn.js → clojure-Dx_acSX7.js} +0 -0
  753. /package/dist/web/assets/{cmake-D1imEXan.js → cmake-CyeYPq8o.js} +0 -0
  754. /package/dist/web/assets/{codeowners-YpylTFB5.js → codeowners-DrTI0lmI.js} +0 -0
  755. /package/dist/web/assets/{codeql-ajDEPgqi.js → codeql-CPkFxVW-.js} +0 -0
  756. /package/dist/web/assets/{common-lisp-C2sCrd-m.js → common-lisp-B0k-3LTj.js} +0 -0
  757. /package/dist/web/assets/{coq-BqybyDc9.js → coq-uygpo7to.js} +0 -0
  758. /package/dist/web/assets/{createLucideIcon-D6fVXe3s.js → createLucideIcon-DoiEKeJ_.js} +0 -0
  759. /package/dist/web/assets/{csharp-nKCEiujy.js → csharp-DKUYGfSp.js} +0 -0
  760. /package/dist/web/assets/{css-CJSp2CsD.js → css-DoOUp7v4.js} +0 -0
  761. /package/dist/web/assets/{csv-Nh9vQwQm.js → csv-B3YE13gi.js} +0 -0
  762. /package/dist/web/assets/{csv-parser-iow1WWML.js → csv-parser-DXePqmXe.js} +0 -0
  763. /package/dist/web/assets/{cue-Ch79RPsU.js → cue-DViO2wvn.js} +0 -0
  764. /package/dist/web/assets/{cypher-CgiOQ6hs.js → cypher-4BMCIW5N.js} +0 -0
  765. /package/dist/web/assets/{d-CmBcySpH.js → d-BT_sTHas.js} +0 -0
  766. /package/dist/web/assets/{dark-plus-COLEX2ui.js → dark-plus-DVVXu-1N.js} +0 -0
  767. /package/dist/web/assets/{dart-BTAArMcJ.js → dart-DHz6f6_6.js} +0 -0
  768. /package/dist/web/assets/{data-grid-types-tNj9zuRV.js → data-grid-types-OgUxMLdQ.js} +0 -0
  769. /package/dist/web/assets/{dax-BbZzRY9i.js → dax-ChYCjweK.js} +0 -0
  770. /package/dist/web/assets/{desktop-OmQvU3-J.js → desktop-D6Lt4p04.js} +0 -0
  771. /package/dist/web/assets/{diff-BEPnb9Fb.js → diff-D8TDe4hP.js} +0 -0
  772. /package/dist/web/assets/{dist-BiqPfYiP.js → dist-CrBOCtCW.js} +0 -0
  773. /package/dist/web/assets/{dist-BDpuZ376.js → dist-DOTlFQJA.js} +0 -0
  774. /package/dist/web/assets/{docker-Cg17HgTD.js → docker-Bsp0YbPx.js} +0 -0
  775. /package/dist/web/assets/{dotenv-DEMIaY7V.js → dotenv-B_7giSY_.js} +0 -0
  776. /package/dist/web/assets/{dracula-BkCFq8SO.js → dracula-TrQL2iB5.js} +0 -0
  777. /package/dist/web/assets/{dracula-soft-Cjp6-ltF.js → dracula-soft-n2XOlgh-.js} +0 -0
  778. /package/dist/web/assets/{dream-maker-Ce7Y_UKj.js → dream-maker-CdIEdhpT.js} +0 -0
  779. /package/dist/web/assets/{emacs-lisp-BjpyfTbN.js → emacs-lisp-CSbixs6u.js} +0 -0
  780. /package/dist/web/assets/{everforest-dark-9r4okw1s.js → everforest-dark-BuZahZYp.js} +0 -0
  781. /package/dist/web/assets/{everforest-light-GCQTZ3Ju.js → everforest-light-DPH53z5H.js} +0 -0
  782. /package/dist/web/assets/{fennel-ITjY5Z6u.js → fennel-B1-K_hlJ.js} +0 -0
  783. /package/dist/web/assets/{fish-DP2uEzyg.js → fish-Cy12Dbua.js} +0 -0
  784. /package/dist/web/assets/{fluent-irzaP1f6.js → fluent-B_82ys8E.js} +0 -0
  785. /package/dist/web/assets/{format-bytes-B9Dbme9a.js → format-bytes-D5sBgIqU.js} +0 -0
  786. /package/dist/web/assets/{fortran-free-form-BAPoainr.js → fortran-free-form-BRQdNzZF.js} +0 -0
  787. /package/dist/web/assets/{gdscript-DJ098V1z.js → gdscript-XBDmLUm9.js} +0 -0
  788. /package/dist/web/assets/{gdshader-DS2Jfy-K.js → gdshader-DhVfB3u4.js} +0 -0
  789. /package/dist/web/assets/{genie-CqfOaFFx.js → genie-X3K29Y49.js} +0 -0
  790. /package/dist/web/assets/{gherkin-CoZ2ucDH.js → gherkin-DKIx-6cX.js} +0 -0
  791. /package/dist/web/assets/{github-dark-BNLgnSO9.js → github-dark-Bf7zdp5s.js} +0 -0
  792. /package/dist/web/assets/{github-dark-default-CYlSggwi.js → github-dark-default-DqsBBV11.js} +0 -0
  793. /package/dist/web/assets/{github-dark-dimmed-DumUysDk.js → github-dark-dimmed-X2gqRz6w.js} +0 -0
  794. /package/dist/web/assets/{github-dark-high-contrast-TKrcDJ2I.js → github-dark-high-contrast-Z7tvbGX4.js} +0 -0
  795. /package/dist/web/assets/{github-light-DVENFHLJ.js → github-light-BKxt-7zZ.js} +0 -0
  796. /package/dist/web/assets/{github-light-default-DtN8AGcE.js → github-light-default-B0liF4d4.js} +0 -0
  797. /package/dist/web/assets/{github-light-high-contrast-NSyF37x4.js → github-light-high-contrast-F4encJwr.js} +0 -0
  798. /package/dist/web/assets/{gleam-hp4NMSb5.js → gleam-Pz2bHgBB.js} +0 -0
  799. /package/dist/web/assets/{gn-Dptw2PEu.js → gn-CgmdNsPU.js} +0 -0
  800. /package/dist/web/assets/{gnuplot-9juRzjwv.js → gnuplot-qLNF5yZ9.js} +0 -0
  801. /package/dist/web/assets/{go-D-QLCda0.js → go-CSDUNhE7.js} +0 -0
  802. /package/dist/web/assets/{groovy-DHKNy6Eh.js → groovy-B3NEnOHF.js} +0 -0
  803. /package/dist/web/assets/{gruvbox-dark-hard-BFD6aQdB.js → gruvbox-dark-hard-DcoftiUX.js} +0 -0
  804. /package/dist/web/assets/{gruvbox-dark-medium-D5llYBNp.js → gruvbox-dark-medium-D1wVSuD8.js} +0 -0
  805. /package/dist/web/assets/{gruvbox-dark-soft-DOztQmNR.js → gruvbox-dark-soft-DWpunymH.js} +0 -0
  806. /package/dist/web/assets/{gruvbox-light-hard-BFGD-8Pd.js → gruvbox-light-hard-rHMvyUdS.js} +0 -0
  807. /package/dist/web/assets/{gruvbox-light-medium-DzoyXgT7.js → gruvbox-light-medium-3O2KiXO7.js} +0 -0
  808. /package/dist/web/assets/{gruvbox-light-soft-B1Ix2zLN.js → gruvbox-light-soft-lNlXJPwH.js} +0 -0
  809. /package/dist/web/assets/{haskell-BnrJ0k7G.js → haskell-aaG3aC0h.js} +0 -0
  810. /package/dist/web/assets/{haxe-DwShHT_-.js → haxe-CPcHhUDd.js} +0 -0
  811. /package/dist/web/assets/{hcl-VOjEibVB.js → hcl-CMQNlR_4.js} +0 -0
  812. /package/dist/web/assets/{hjson-BHEn7pNn.js → hjson-DeuklPFj.js} +0 -0
  813. /package/dist/web/assets/{hlsl-CrvxlGii.js → hlsl-BI3hlsaM.js} +0 -0
  814. /package/dist/web/assets/{horizon-D3T-P0u6.js → horizon-C7-gOcjs.js} +0 -0
  815. /package/dist/web/assets/{horizon-bright-vifGsEdk.js → horizon-bright-Cxt9bRW7.js} +0 -0
  816. /package/dist/web/assets/{houston-IrOgT7r4.js → houston-DbF2G7iD.js} +0 -0
  817. /package/dist/web/assets/{hy-5eSgTJ8G.js → hy-DRG6Hs2I.js} +0 -0
  818. /package/dist/web/assets/{image-gallery-COea0nSk.js → image-gallery-CObzTisL.js} +0 -0
  819. /package/dist/web/assets/{imba-Dp0I4DVl.js → imba-CxKv_2TW.js} +0 -0
  820. /package/dist/web/assets/{ini-CHDKad9O.js → ini-BiHsyCdO.js} +0 -0
  821. /package/dist/web/assets/{java-YLv7IDJ1.js → java-D3RZzgDB.js} +0 -0
  822. /package/dist/web/assets/{javascript-jr4kIdgl.js → javascript-4FmSqG-T.js} +0 -0
  823. /package/dist/web/assets/{json-EJWSOZfp.js → json-CjB236_E.js} +0 -0
  824. /package/dist/web/assets/{json5-D6f_uNwJ.js → json5-bkfO-Rco.js} +0 -0
  825. /package/dist/web/assets/{jsonc-DrHCdmos.js → jsonc-BwaTeLyv.js} +0 -0
  826. /package/dist/web/assets/{jsonl-Cwc3cXEL.js → jsonl-CfxNSwGd.js} +0 -0
  827. /package/dist/web/assets/{jsonnet-BwZfaIFT.js → jsonnet-CQETCYrp.js} +0 -0
  828. /package/dist/web/assets/{jssm-BpEmg_MT.js → jssm-D5o3Zzdz.js} +0 -0
  829. /package/dist/web/assets/{jsx-DBGPwcr4.js → jsx-BAMILPLo.js} +0 -0
  830. /package/dist/web/assets/{kanagawa-dragon-DRfVaGIX.js → kanagawa-dragon-BhGVxu3R.js} +0 -0
  831. /package/dist/web/assets/{kanagawa-lotus-CjOlny0Q.js → kanagawa-lotus-FH4Y59ro.js} +0 -0
  832. /package/dist/web/assets/{kanagawa-wave-DvdM88a4.js → kanagawa-wave-eilvP9MB.js} +0 -0
  833. /package/dist/web/assets/{kdl-DSo3PvM0.js → kdl-BQ-yvXUK.js} +0 -0
  834. /package/dist/web/assets/{kotlin-BV8TwCGu.js → kotlin-BuCx0-VT.js} +0 -0
  835. /package/dist/web/assets/{kusto-DCo1Fdmm.js → kusto-DAN_n_Cy.js} +0 -0
  836. /package/dist/web/assets/{laserwave-DPX9FvD9.js → laserwave-Cq2BlxrT.js} +0 -0
  837. /package/dist/web/assets/{lean-Cya8dqZu.js → lean-Dhq7uXKw.js} +0 -0
  838. /package/dist/web/assets/{less-CJUPgkIs.js → less-CxaMQb8j.js} +0 -0
  839. /package/dist/web/assets/{lib-DUBAP_Gd.js → lib-BVwmtv2i.js} +0 -0
  840. /package/dist/web/assets/{libesm-CcPYXCKp.js → libesm-DdtEjhIi.js} +0 -0
  841. /package/dist/web/assets/{light-plus-BXXrlFJH.js → light-plus-B4sMAend.js} +0 -0
  842. /package/dist/web/assets/{llvm-CUbQanZr.js → llvm-DQ6-B_6Y.js} +0 -0
  843. /package/dist/web/assets/{log-B3fFlSMd.js → log-Bb_vqvu7.js} +0 -0
  844. /package/dist/web/assets/{logo-ucAyw6zf.js → logo-BzfYv3A_.js} +0 -0
  845. /package/dist/web/assets/{luau-C4l6TSTB.js → luau-BnAeNR77.js} +0 -0
  846. /package/dist/web/assets/{make-CDtGIVc7.js → make-B6IFuxpC.js} +0 -0
  847. /package/dist/web/assets/{markdown-CH1zKu2p.js → markdown-Z0xi77Fn.js} +0 -0
  848. /package/dist/web/assets/{material-theme-CEuGVTmh.js → material-theme-1scy1SYn.js} +0 -0
  849. /package/dist/web/assets/{material-theme-darker-CUZ3MoIr.js → material-theme-darker-G-JykXQP.js} +0 -0
  850. /package/dist/web/assets/{material-theme-lighter-DxAqJSid.js → material-theme-lighter-BtKkCMVq.js} +0 -0
  851. /package/dist/web/assets/{material-theme-ocean-BSvvHJp6.js → material-theme-ocean-irrDC89L.js} +0 -0
  852. /package/dist/web/assets/{material-theme-palenight-QVOl50_m.js → material-theme-palenight-Eq5_K4LM.js} +0 -0
  853. /package/dist/web/assets/{matlab-CZLQLwUp.js → matlab-BE3oCXZC.js} +0 -0
  854. /package/dist/web/assets/{mdx-DO4NJsOo.js → mdx-DYiuFTGw.js} +0 -0
  855. /package/dist/web/assets/{mermaid-CIwzg1uO.js → mermaid-DVlaEvtw.js} +0 -0
  856. /package/dist/web/assets/{min-dark-BdIgslXy.js → min-dark-DSkeTpQa.js} +0 -0
  857. /package/dist/web/assets/{min-light-DyU-T1r2.js → min-light-CFv2Q0uF.js} +0 -0
  858. /package/dist/web/assets/{mipsasm-DRad-Klm.js → mipsasm-CiAFuZQ2.js} +0 -0
  859. /package/dist/web/assets/{mojo-DXp5LDOc.js → mojo-ARshcaSQ.js} +0 -0
  860. /package/dist/web/assets/{monokai-BiKX3qby.js → monokai-CPTV-SOg.js} +0 -0
  861. /package/dist/web/assets/{moonbit-BOUoLF5r.js → moonbit-D57B0dZr.js} +0 -0
  862. /package/dist/web/assets/{move-Dlk64FWK.js → move-qgy3G_o9.js} +0 -0
  863. /package/dist/web/assets/{narrat-B-qd2Lh0.js → narrat-CZ3rC-oc.js} +0 -0
  864. /package/dist/web/assets/{nextflow-groovy-iAMYas0F.js → nextflow-groovy-BxxIOcI4.js} +0 -0
  865. /package/dist/web/assets/{night-owl-3YNqjftR.js → night-owl-B1ryGCda.js} +0 -0
  866. /package/dist/web/assets/{night-owl-light-GIncHb5w.js → night-owl-light-BZvNnvMF.js} +0 -0
  867. /package/dist/web/assets/{nix-xjlEb3uw.js → nix-BVBQi8yv.js} +0 -0
  868. /package/dist/web/assets/{nord-C8tvWx7P.js → nord-3NXw613h.js} +0 -0
  869. /package/dist/web/assets/{nushell-BqBb4pU6.js → nushell-BMOLRalb.js} +0 -0
  870. /package/dist/web/assets/{objective-c-Bk6s0Cn5.js → objective-c-AxruEUgh.js} +0 -0
  871. /package/dist/web/assets/{objective-cpp-DGTVWQ3V.js → objective-cpp-cFT0qRsD.js} +0 -0
  872. /package/dist/web/assets/{ocaml-D6wPQ4kw.js → ocaml-_8JH6O8J.js} +0 -0
  873. /package/dist/web/assets/{odin-C9mw2xRN.js → odin-nwYvR0O6.js} +0 -0
  874. /package/dist/web/assets/{one-dark-pro-SMBlneFb.js → one-dark-pro-C1LaYJaH.js} +0 -0
  875. /package/dist/web/assets/{one-light-C9JMU1Cx.js → one-light-B7rMc__c.js} +0 -0
  876. /package/dist/web/assets/{openscad-C0I9CtuA.js → openscad-Cwes8bKO.js} +0 -0
  877. /package/dist/web/assets/{pascal-D0z4DJnY.js → pascal-DMt-6tdZ.js} +0 -0
  878. /package/dist/web/assets/{pkl-CITYNGVz.js → pkl-QBTyYQmB.js} +0 -0
  879. /package/dist/web/assets/{plastic-Bf0T6rmg.js → plastic-BTSUp8Fk.js} +0 -0
  880. /package/dist/web/assets/{plsql-Chr907pu.js → plsql-DQv3IFXP.js} +0 -0
  881. /package/dist/web/assets/{po-07U75CYV.js → po-CNwZx4tW.js} +0 -0
  882. /package/dist/web/assets/{poimandres-B0T4qwXY.js → poimandres-8Os7Qequ.js} +0 -0
  883. /package/dist/web/assets/{polar-BvNJMMV5.js → polar-b4yK8oD8.js} +0 -0
  884. /package/dist/web/assets/{portal-container-context-Bqn516X4.js → portal-container-context-BarKmT4S.js} +0 -0
  885. /package/dist/web/assets/{postcss-BSdf-zzo.js → postcss-sFctwpxx.js} +0 -0
  886. /package/dist/web/assets/{powerquery-B2Ad_HK8.js → powerquery-Cg_WrtuN.js} +0 -0
  887. /package/dist/web/assets/{powershell-DO5WY2v2.js → powershell-C7Ifz95h.js} +0 -0
  888. /package/dist/web/assets/{prisma-BfaRPYzs.js → prisma-Z52HUJzQ.js} +0 -0
  889. /package/dist/web/assets/{prolog-Cz0Nue9e.js → prolog-DLqW-zR1.js} +0 -0
  890. /package/dist/web/assets/{proto-FVleUZJO.js → proto-BOIzxCCJ.js} +0 -0
  891. /package/dist/web/assets/{puppet-BEX5o7hE.js → puppet-De7TjZIf.js} +0 -0
  892. /package/dist/web/assets/{purescript-BZX6Kw4j.js → purescript-R76ou1WT.js} +0 -0
  893. /package/dist/web/assets/{python-CO89UIxE.js → python-Dprm0tNP.js} +0 -0
  894. /package/dist/web/assets/{qmldir-DH526DWB.js → qmldir-Clp_jpNw.js} +0 -0
  895. /package/dist/web/assets/{qss-wp8im4Iz.js → qss-eQPYYzSo.js} +0 -0
  896. /package/dist/web/assets/{r-F9n--bvq.js → r-CCcRHIf5.js} +0 -0
  897. /package/dist/web/assets/{racket-BQvbOE5D.js → racket-CzE472yp.js} +0 -0
  898. /package/dist/web/assets/{raku-B0iWSynj.js → raku-DQwXrTSi.js} +0 -0
  899. /package/dist/web/assets/{react-Bcm1Oab0.js → react-Bghu0NUO.js} +0 -0
  900. /package/dist/web/assets/{red-Cm00r9eI.js → red-KCS6wyop.js} +0 -0
  901. /package/dist/web/assets/{reg-CBuhna22.js → reg-CcrAddUh.js} +0 -0
  902. /package/dist/web/assets/{regexp-C9j1_6oU.js → regexp-DDbud-Ri.js} +0 -0
  903. /package/dist/web/assets/{rel-BYzlBhbq.js → rel-99GDgaHp.js} +0 -0
  904. /package/dist/web/assets/{resolve-theme-Dm8ehbOv.js → resolve-theme-CW2niGV2.js} +0 -0
  905. /package/dist/web/assets/{riscv-CQQP0Ro9.js → riscv-Dm2eNv0R.js} +0 -0
  906. /package/dist/web/assets/{ron-j1ON9YzM.js → ron-CzFt0ygH.js} +0 -0
  907. /package/dist/web/assets/{rose-pine-Cbri3jon.js → rose-pine-Ck6lCrCP.js} +0 -0
  908. /package/dist/web/assets/{rose-pine-dawn-BMuvKrGo.js → rose-pine-dawn-vtALRJNj.js} +0 -0
  909. /package/dist/web/assets/{rose-pine-moon-4biZzaYD.js → rose-pine-moon-DPto8qXw.js} +0 -0
  910. /package/dist/web/assets/{rosmsg-CXx22rtz.js → rosmsg-NtS6429H.js} +0 -0
  911. /package/dist/web/assets/{rust-D28cFIhL.js → rust-BjIXvF9h.js} +0 -0
  912. /package/dist/web/assets/{sass-DtNw4V28.js → sass-CQ23wf5E.js} +0 -0
  913. /package/dist/web/assets/{scala-hv69_YQC.js → scala-LfKLM143.js} +0 -0
  914. /package/dist/web/assets/{scheme-Dtp9B_ng.js → scheme-D3SLBXlF.js} +0 -0
  915. /package/dist/web/assets/{sdbl-DEwEngAE.js → sdbl-ODRd2rGN.js} +0 -0
  916. /package/dist/web/assets/{shellscript-CCOF9tox.js → shellscript-3KB1G_oA.js} +0 -0
  917. /package/dist/web/assets/{slack-dark-DT0E0ABt.js → slack-dark-DTtyWmIy.js} +0 -0
  918. /package/dist/web/assets/{slack-ochin-DHHiQnxo.js → slack-ochin-Bu0PApk3.js} +0 -0
  919. /package/dist/web/assets/{smalltalk-BF_I35xJ.js → smalltalk-haMT-Sbe.js} +0 -0
  920. /package/dist/web/assets/{snazzy-light-Cv7tIZo9.js → snazzy-light-DXJzefE9.js} +0 -0
  921. /package/dist/web/assets/{solarized-dark-wZ5YPCys.js → solarized-dark-B6LyYa18.js} +0 -0
  922. /package/dist/web/assets/{solarized-light-DGDOr1AS.js → solarized-light-QLADebkv.js} +0 -0
  923. /package/dist/web/assets/{solidity-BJknuPqJ.js → solidity-mzfq1aDc.js} +0 -0
  924. /package/dist/web/assets/{splunk-B1ClO_KU.js → splunk-CyH4RWBp.js} +0 -0
  925. /package/dist/web/assets/{sql-CXZ66NtF.js → sql-CWVlT5D3.js} +0 -0
  926. /package/dist/web/assets/{ssh-config-kgM67Sf6.js → ssh-config-DNvQJ1Qx.js} +0 -0
  927. /package/dist/web/assets/{stylus-RcVaInR-.js → stylus-ByQQdtmf.js} +0 -0
  928. /package/dist/web/assets/{swift-Cm0fh2lX.js → swift-D1cpY4ow.js} +0 -0
  929. /package/dist/web/assets/{synthwave-84-CwLiazxr.js → synthwave-84-BDF_-QiW.js} +0 -0
  930. /package/dist/web/assets/{system-verilog-CzYaDVLH.js → system-verilog-BYrkMydC.js} +0 -0
  931. /package/dist/web/assets/{systemd-DbSagfLw.js → systemd-CQm3zyK0.js} +0 -0
  932. /package/dist/web/assets/{talonscript-BCFnWejL.js → talonscript-DMW5CzE1.js} +0 -0
  933. /package/dist/web/assets/{tasl-BUdqtEe1.js → tasl-BhFzMCr2.js} +0 -0
  934. /package/dist/web/assets/{tcl-CR_lTV9N.js → tcl-D7VEAF-9.js} +0 -0
  935. /package/dist/web/assets/{terraform-Bqsha-xe.js → terraform-DdT5A0Vy.js} +0 -0
  936. /package/dist/web/assets/{tokyo-night-Cnuo6JB6.js → tokyo-night-WGLMM6Q9.js} +0 -0
  937. /package/dist/web/assets/{toml-CLFdhT22.js → toml-CZ7ipXHZ.js} +0 -0
  938. /package/dist/web/assets/{tsv-Dw3tLrZN.js → tsv-B1zVvoTr.js} +0 -0
  939. /package/dist/web/assets/{tsx-B8k1sFqw.js → tsx-D4HwRUBq.js} +0 -0
  940. /package/dist/web/assets/{turtle-BMKMfTrj.js → turtle-B4r8KEJH.js} +0 -0
  941. /package/dist/web/assets/{typescript-D38LfGSl.js → typescript-BnHhxMtp.js} +0 -0
  942. /package/dist/web/assets/{typespec-EbgGJHV_.js → typespec-DP2q46GT.js} +0 -0
  943. /package/dist/web/assets/{typst-CQXV2SzX.js → typst-DFsD_yaD.js} +0 -0
  944. /package/dist/web/assets/{use-is-mobile-Dypg-2ZO.js → use-is-mobile-BQ9Bv5Ga.js} +0 -0
  945. /package/dist/web/assets/{use-visual-viewport-Bj3QHRkF.js → use-visual-viewport-CbwnktSG.js} +0 -0
  946. /package/dist/web/assets/{utils-CwNUra6_.js → utils-Bmf_8h7Y.js} +0 -0
  947. /package/dist/web/assets/{v-Ci1jObJJ.js → v-Bji8hSVT.js} +0 -0
  948. /package/dist/web/assets/{vala-C_NdfqrY.js → vala-D3TIgGmU.js} +0 -0
  949. /package/dist/web/assets/{vb-K_r_h_s8.js → vb-DvcxFS2q.js} +0 -0
  950. /package/dist/web/assets/{vendor-xterm-BtjgHRUe.js → vendor-xterm-BqPDsE9b.js} +0 -0
  951. /package/dist/web/assets/{verilog-DQPYLsZI.js → verilog-Dy7cF35d.js} +0 -0
  952. /package/dist/web/assets/{vesper-BrmzEV8s.js → vesper-BzjwUv9J.js} +0 -0
  953. /package/dist/web/assets/{vhdl-CjxQGaH2.js → vhdl-DuFnFNmW.js} +0 -0
  954. /package/dist/web/assets/{viml-CTKJNQH1.js → viml-yAPgKlbz.js} +0 -0
  955. /package/dist/web/assets/{vitesse-black-i-H_3p1i.js → vitesse-black-oSSQdyIZ.js} +0 -0
  956. /package/dist/web/assets/{vitesse-dark-DZC_Yy3_.js → vitesse-dark-BZxwPihB.js} +0 -0
  957. /package/dist/web/assets/{vitesse-light-rf4kpmk8.js → vitesse-light-uIigycvt.js} +0 -0
  958. /package/dist/web/assets/{vyper-BLnOw4Nf.js → vyper-CX3NAxm5.js} +0 -0
  959. /package/dist/web/assets/{wasm-DbLMmiW0.js → wasm-6mEOOvVa.js} +0 -0
  960. /package/dist/web/assets/{wasm-CWeHvGNG.js → wasm-D9td37qO.js} +0 -0
  961. /package/dist/web/assets/{wenyan-CLwdRuzd.js → wenyan-lwNIHgk6.js} +0 -0
  962. /package/dist/web/assets/{wgsl-CBTDMVJR.js → wgsl-DQAzCfog.js} +0 -0
  963. /package/dist/web/assets/{wikitext-CQHrF84e.js → wikitext-DTXmt5NE.js} +0 -0
  964. /package/dist/web/assets/{wit-8xdbdipU.js → wit-Bdw_7WDH.js} +0 -0
  965. /package/dist/web/assets/{wolfram-tFwx32a_.js → wolfram-DqyHDZ07.js} +0 -0
  966. /package/dist/web/assets/{yaml-BP8N91fU.js → yaml-CPXhdByd.js} +0 -0
  967. /package/dist/web/assets/{zenscript-B10Pj7JT.js → zenscript-6s_i5zek.js} +0 -0
  968. /package/dist/web/assets/{zig-BZKcTtrK.js → zig-B2htezJ_.js} +0 -0
@@ -2,7 +2,8 @@
2
2
  * Generate the complete webview HTML for the git graph panel.
3
3
  * All JS + CSS is inlined since webview runs in an iframe sandbox.
4
4
  */
5
- import { AVATAR_JS } from "./webview-shell.ts";
5
+ import { AVATAR_JS, FONT_TOKENS } from "./webview-shell.ts";
6
+ import { COMMIT_MESSAGE_JS } from "./commit-message-html.ts";
6
7
 
7
8
  export function getWebviewHtml(): string {
8
9
  return `<!DOCTYPE html>
@@ -81,10 +82,13 @@ ${getStyles()}
81
82
  <button id="find-close" title="Close">&times;</button>
82
83
  </div>
83
84
  <div id="search-results" class="search-results hidden"></div>
85
+ <div id="graph-area">
84
86
  <div id="graph-container">
85
87
  <div id="graph-header" class="commit-row header-row">
88
+ <div class="col-refs">Branch / Tag</div>
86
89
  <div class="col-graph">Graph<div class="graph-resize-handle" id="graph-resize-handle"></div></div>
87
90
  <div class="col-message">Message</div>
91
+ <div class="col-changes">Changes</div>
88
92
  <div class="col-author">Author</div>
89
93
  <div class="col-date">Date</div>
90
94
  <div class="col-hash">Hash</div>
@@ -94,6 +98,8 @@ ${getStyles()}
94
98
  <div id="commit-list"></div>
95
99
  </div>
96
100
  <div id="loading" class="loading hidden">Loading...</div>
101
+ </div>
102
+ <div id="scroll-markers" aria-hidden="true"></div>
97
103
  </div>
98
104
  <div id="detail-panel" class="detail-panel hidden"></div>
99
105
  <div id="settings-panel" class="settings-panel">
@@ -161,6 +167,24 @@ ${getScript()}
161
167
  </html>`;
162
168
  }
163
169
 
170
+ /**
171
+ * Dark values, emitted twice by design.
172
+ *
173
+ * The panel is a sandboxed iframe and cannot see the app's theme, so on its own
174
+ * the only question it can ask is prefers-color-scheme — the *OS* setting,
175
+ * which is why a light app still showed a dark graph. The host now stamps
176
+ * data-ppm-theme on this document and injects the app's own tokens
177
+ * (src/web/components/extensions/webview-theme.ts); the media query is the
178
+ * fallback for a host that says nothing, and it must not override an explicit
179
+ * light. Values are a charcoal rather than near-black so the toolbar, the list
180
+ * and the banded rows read as three surfaces instead of one slab.
181
+ */
182
+ const DARK_TOKENS = `
183
+ --bg: #16171c; --surface: #1d1f26; --text: #ecedf0; --subtext: #a2a5b0; --subtle: #6b6f7c;
184
+ --border: #262932; --border2: #383c48; --selected: #1e293b;
185
+ --band: color-mix(in srgb, var(--text) 6%, transparent);
186
+ `;
187
+
164
188
  function getStyles(): string {
165
189
  return `
166
190
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -168,15 +192,28 @@ function getStyles(): string {
168
192
  --bg: #ffffff; --surface: #f4f4f5; --text: #09090b; --subtext: #71717a; --subtle: #a1a1aa;
169
193
  --border: #e4e4e7; --border2: #d4d4d8; --blue: #3b82f6; --red: #ef4444; --green: #22c55e;
170
194
  --yellow: #eab308; --purple: #8b5cf6; --orange: #f97316;
171
- --surface-hover: #f4f4f5; --selected: #eff6ff;
172
- }
195
+ --selected: #eff6ff;
196
+ /* Zebra banding. Stronger in dark below, because the same percentage is not
197
+ equally visible in both directions: a wash of near-black over white shows
198
+ up where the same lift of near-white over a near-black row does not, which
199
+ is what left the list looking like one flat slab. */
200
+ --band: color-mix(in srgb, var(--text) 3.5%, transparent);
201
+ /* Derived, never injected: the app has no hover token and some of its themes
202
+ give the same colour to both panel surfaces, which would leave a hovered
203
+ row looking untouched. A tint of the text colour flips with the mode and
204
+ stacks on top of the zebra banding instead of replacing it. */
205
+ --surface-hover: color-mix(in srgb, var(--text) 8%, transparent);
206
+ /* Width of the branch/tag column. Fixed, because the graph is one SVG overlay
207
+ drawn on a single grid and it starts where this column ends. */
208
+ --refs-col-w: 170px;
209
+
210
+ ${FONT_TOKENS}
211
+ }
212
+ :root[data-ppm-theme="dark"] { ${DARK_TOKENS} }
173
213
  @media (prefers-color-scheme: dark) {
174
- :root {
175
- --bg: #09090b; --surface: #18181b; --text: #fafafa; --subtext: #a1a1aa; --subtle: #52525b;
176
- --border: #27272a; --border2: #3f3f46; --selected: #1e293b; --surface-hover: #27272a;
177
- }
214
+ :root:not([data-ppm-theme="light"]) { ${DARK_TOKENS} }
178
215
  }
179
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); font-size: 12px; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
216
+ body { font-family: var(--ui-font); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background: var(--bg); color: var(--text); font-size: 12px; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
180
217
  #app { display: flex; flex-direction: column; height: 100vh; }
181
218
 
182
219
  /* Toolbar */
@@ -268,25 +305,67 @@ button:active { background: var(--surface); }
268
305
  #find-count { font-size: 10px; color: var(--subtext); min-width: 50px; }
269
306
  .hidden { display: none !important; }
270
307
 
271
- /* Graph container */
272
- #graph-container { flex: 1; overflow-y: auto; overflow-x: hidden; }
273
- .commit-row { display: flex; align-items: center; cursor: pointer; height: 24px; padding: 0 6px; font-size: 12px; box-sizing: border-box; overflow: hidden; }
308
+ /* Graph container. The scroller is wrapped, because the scroll markers have to
309
+ sit beside it in a box that does not scroll with the rows. */
310
+ #graph-area { position: relative; flex: 1; min-height: 0; display: flex; }
311
+ #graph-container { flex: 1; min-width: 0; overflow-y: auto; overflow-x: hidden; }
312
+ /* Where in the whole history the things worth scrolling to are: the checked-out
313
+ commit, the selected row, and every search match. Over the scrollbar, like
314
+ VS Code's own overview ruler, and never clickable — dragging the scrollbar
315
+ underneath has to keep working. */
316
+ #scroll-markers { position: absolute; right: 0; top: 0; bottom: 0; width: 5px; pointer-events: none; z-index: 3; }
317
+ .scroll-marker { position: absolute; right: 0; width: 5px; height: 2px; border-radius: 1px; transform: translateY(-50%); }
318
+ .sm-search { background: var(--yellow); }
319
+ .sm-head { background: var(--green); height: 3px; }
320
+ .sm-selected { background: var(--blue); height: 3px; }
321
+ .commit-row { display: flex; align-items: center; cursor: pointer; height: 30px; padding: 0 6px; font-size: 12px; box-sizing: border-box; overflow: hidden; }
322
+ /* Banding, before the hover and selected rules on purpose: it has the same
323
+ specificity as they do, so source order is what decides the winner. Do not
324
+ qualify it with #commit-list — an id would raise it above both of them and
325
+ every other row would stop showing hover and selection. The header row is a
326
+ .commit-row too but is the first child of its own parent, so it is odd. */
327
+ .commit-row:nth-child(even) { background: var(--band); }
274
328
  .commit-row:hover { background: var(--surface-hover); }
275
- .commit-row.selected { background: var(--selected); }
276
- .commit-row.header-row { background: var(--surface); cursor: default; font-weight: 600; font-size: 10px; color: var(--subtext); text-transform: uppercase; letter-spacing: 0.5px; position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--border); min-height: 22px; }
277
- .commit-row.search-match { background: rgba(234, 179, 8, 0.15); }
329
+ .commit-row.header-row { background: var(--surface); cursor: default; font-weight: 600; font-size: 10px; color: var(--subtext); text-transform: uppercase; letter-spacing: 0.5px; position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--border); height: 24px; }
330
+ .commit-row.header-row .col-message::before { display: none; }
331
+ /* A tint and a bar, not a flood: with fifteen rows on screen a 15% yellow
332
+ wash over half of them buried the list it was meant to annotate. The
333
+ scroll markers carry where the rest of the matches are. */
334
+ .commit-row.search-match { background: color-mix(in srgb, var(--yellow) 9%, transparent); box-shadow: inset 2px 0 0 var(--yellow); }
335
+ /* After the match rule on purpose: the row you clicked should look selected,
336
+ even when it is also a match. Which of the two it is stays visible in the
337
+ find bar's count and in the scroll markers. */
338
+ .commit-row.selected { background: var(--selected); box-shadow: inset 2px 0 0 var(--blue); }
278
339
  .commit-row.virtual { opacity: 0.85; font-style: italic; }
279
340
  .commit-row.virtual .col-message { color: var(--subtext); }
280
341
  .commit-row.stash-row { opacity: 0.75; }
281
342
  .commit-row.stash-row .col-message { color: var(--subtext); font-style: italic; }
282
343
  .file-clickable { cursor: pointer; border-radius: 3px; padding: 2px 4px; margin: 0 -4px; }
283
344
  .file-clickable:hover { background: var(--surface-hover); }
345
+ /* Refs get a column of their own so every message starts at the same x — a
346
+ branch badge pushing the text right was most of why the list read as ragged.
347
+ The width has to be fixed rather than content-sized: rows are separate flex
348
+ containers, so an auto width would put each row's graph at a different x, and
349
+ the graph is one SVG overlay drawn on a single grid. */
350
+ .col-refs { width: var(--refs-col-w, 170px); min-width: var(--refs-col-w, 170px); flex-shrink: 0; overflow: hidden; white-space: nowrap; display: flex; align-items: center; justify-content: flex-end; gap: 3px; padding-right: 6px; }
351
+ .col-refs .ref-badge { min-width: 0; overflow: hidden; text-overflow: ellipsis; margin-right: 0; }
284
352
  .col-graph { width: var(--graph-col-w, 120px); min-width: var(--graph-col-w, 80px); overflow: hidden; flex-shrink: 0; position: relative; }
285
353
  .graph-resize-handle { position: absolute; right: 0; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 3; background: transparent; }
286
354
  .graph-resize-handle:hover, .graph-resize-handle.dragging { background: var(--blue); opacity: 0.5; }
287
- .col-message { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 6px; }
288
- .col-author { width: 100px; min-width: 100px; overflow: hidden; white-space: nowrap; color: var(--subtext); font-size: 11px; display: flex; align-items: center; gap: 4px; }
289
- .col-author .author-name { overflow: hidden; text-overflow: ellipsis; }
355
+ .col-message { flex: 1; min-width: 0; padding: 0 6px 0 12px; position: relative; display: flex; flex-direction: column; justify-content: center; align-self: stretch; overflow: hidden; }
356
+ /* The lane's colour, restated where the eye reads the message. The row centres
357
+ its cells, so this one has to stretch or the tick has nothing to span. */
358
+ .col-message::before { content: ''; position: absolute; left: 3px; top: 7px; bottom: 7px; width: 2px; border-radius: 1px; background: var(--lane, transparent); opacity: 0.65; }
359
+ .msg-subject { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
360
+ /* Author, date and hash again, for the phone layout that has no room for their
361
+ columns. Hidden until that layout asks for it. */
362
+ .msg-meta { display: none; }
363
+ /* Lines added and removed. Tabular figures so the columns of digits line up
364
+ down the list rather than jittering with the glyph widths. */
365
+ .col-changes { width: 84px; min-width: 84px; flex-shrink: 0; font-size: 11px; font-variant-numeric: tabular-nums; display: flex; align-items: center; gap: 5px; }
366
+ .col-changes .ch-add { color: var(--green); }
367
+ .col-changes .ch-del { color: var(--red); }
368
+ .col-author { width: 130px; min-width: 130px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--subtext); font-size: 11px; }
290
369
  .avatar { width: 16px; height: 16px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 7px; font-weight: 700; color: #fff; flex-shrink: 0; letter-spacing: -0.2px; }
291
370
 
292
371
  /* Author hover card */
@@ -306,9 +385,9 @@ button:active { background: var(--surface); }
306
385
  .sr-item:hover { background: var(--surface-hover); }
307
386
  .sr-subject { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
308
387
  .sr-meta { font-size: 10px; color: var(--subtext); display: flex; gap: 6px; align-items: center; margin-top: 2px; }
309
- .sr-hash { font-family: 'SF Mono', 'Fira Code', monospace; }
388
+ .sr-hash { font-family: var(--mono-font); }
310
389
  .col-date { width: 80px; min-width: 80px; color: var(--subtext); font-size: 11px; }
311
- .col-hash { width: 60px; min-width: 60px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 10px; color: var(--subtle); }
390
+ .col-hash { width: 60px; min-width: 60px; font-family: var(--mono-font); font-size: 10px; color: var(--subtle); }
312
391
 
313
392
  /* Ref badges — border + tinted bg + dark text */
314
393
  .ref-badge { display: inline-flex; align-items: center; gap: 3px; padding: 0px 5px; border-radius: 3px; font-size: 9px; font-weight: 600; margin-right: 3px; vertical-align: middle; line-height: 16px; border: 1px solid; color: #1a1a1a; }
@@ -317,26 +396,129 @@ button:active { background: var(--surface); }
317
396
  .ref-remote { border-color: var(--purple); background: color-mix(in srgb, var(--purple) 12%, transparent); }
318
397
  .ref-tag { border-color: var(--yellow); background: color-mix(in srgb, var(--yellow) 12%, transparent); }
319
398
  .ref-stash { border-color: #808080; background: color-mix(in srgb, #808080 12%, transparent); }
399
+ :root[data-ppm-theme="dark"] .ref-badge { color: #e4e4e7; }
320
400
  @media (prefers-color-scheme: dark) {
321
- .ref-badge { color: #e4e4e7; }
401
+ :root:not([data-ppm-theme="light"]) .ref-badge { color: #e4e4e7; }
322
402
  }
323
403
 
324
404
  /* SVG graph — single SVG overlay */
325
405
  #commit-list-wrapper { position: relative; }
326
- #graph-svg-container { position: absolute; top: 0; left: 8px; z-index: 1; pointer-events: none; }
406
+ #graph-svg-container { position: absolute; top: 0; left: calc(var(--refs-col-w, 170px) + 8px); z-index: 1; pointer-events: none; }
327
407
  #graph-svg-container circle { pointer-events: auto; cursor: pointer; }
328
408
  #graph-svg-container .line { stroke-width: 2; fill: none; }
329
- #graph-svg-container .graphCurrent { fill: var(--bg); stroke-width: 2; }
409
+ /* The checked-out commit wears a thicker ring rather than a different shape,
410
+ so it still reads as the same kind of thing as every row above it. */
411
+ #graph-svg-container .graphCurrent { stroke-width: 3.5; }
412
+ #graph-svg-container .node-initials { font-size: 8px; font-weight: 700; fill: #fff; pointer-events: none; user-select: none; letter-spacing: -0.3px; }
330
413
  .commit-row.graph-hover { background: var(--surface-hover); }
331
414
 
332
- /* Detail panel */
333
- .detail-panel { border-top: 1px solid var(--border2); background: var(--surface); max-height: 40vh; overflow-y: auto; padding: 8px 12px; flex-shrink: 0; }
415
+ /* Detail panel. The panel itself carries no padding, because the header is a
416
+ full-width sticky bar and each pane below it pads itself; anything else
417
+ written into the panel wraps itself in .detail-pad. */
418
+ .detail-panel { border-top: 1px solid var(--border2); background: var(--surface); max-height: 40vh; overflow-y: auto; flex-shrink: 0; }
334
419
  .detail-panel h3 { font-size: 13px; margin-bottom: 6px; }
335
- .detail-field { margin-bottom: 3px; font-size: 11px; }
336
- .detail-field .label { color: var(--subtext); display: inline-block; width: 80px; }
337
- .detail-message { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 6px; margin: 6px 0; font-size: 11px; white-space: pre-wrap; font-family: 'SF Mono', 'Fira Code', monospace; }
338
- .file-list { margin-top: 8px; }
339
- .file-item { display: flex; align-items: center; gap: 5px; padding: 1px 0; font-size: 11px; font-family: 'SF Mono', 'Fira Code', monospace; }
420
+ .detail-pad { padding: 8px 12px; }
421
+
422
+ .detail-head { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-bottom: 1px solid var(--border); background: var(--surface); position: sticky; top: 0; z-index: 2; flex-shrink: 0; }
423
+ .detail-head .avatar { width: 20px; height: 20px; font-size: 8px; }
424
+ .detail-who { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
425
+ .detail-author { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
426
+ .detail-when { font-size: 11px; color: var(--subtext); white-space: nowrap; }
427
+ .detail-head-actions { margin-left: auto; display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
428
+
429
+ /* The panel's only dismiss. Escape closes it too, but a touch screen has no
430
+ Escape key, so a panel opened by a tap could not be put away at all — it
431
+ sits over a third of the graph until another commit is tapped. It took the
432
+ place of the two hash chips, which were a convenience: the metadata grid
433
+ two lines below carries the hash and the parents in full.
434
+
435
+ It stays 24px of ink on every device. The generic pointer: coarse button rule
436
+ further down took it to 32, which is still under the 44 a finger needs, and
437
+ growing the button itself would grow the header with it - this sits in a
438
+ sticky bar above a panel that is already capped at 35vh on a phone. So the
439
+ tap area grows instead, through an invisible overlay, which is the same thing
440
+ the app does for its own 20px controls. Nothing else lives in
441
+ .detail-head-actions, and what it reaches on the left is the committed-when
442
+ text, which is not clickable. */
443
+ .detail-close { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; border: none; border-radius: 5px; background: none; color: var(--subtext); }
444
+ .detail-close:hover { background: var(--surface-hover); color: var(--text); }
445
+
446
+ /* Two columns when there is room. A commit message is hard-wrapped by whoever
447
+ wrote it, so on a wide panel it fills half the width and the rest of the row
448
+ is empty; the file list goes there instead of below the fold. Each pane pads
449
+ itself, because above the breakpoint each one also scrolls itself. */
450
+ .detail-grid { display: grid; grid-template-columns: minmax(0, 1fr); }
451
+ .detail-msg { padding: 12px 16px 16px; min-width: 0; }
452
+ .detail-files { padding: 8px 12px 12px; min-width: 0; }
453
+ /* Full hashes, both emails and both dates, in a label/value grid. One rule
454
+ under it separates the provenance from the message; the subject carries none
455
+ of its own, because two hairlines in a 360px panel is a lot of furniture and
456
+ 14px semibold against 11.5px monospace already reads as two things. The rule
457
+ belongs here rather than to the body, which is capped at a readable measure
458
+ and would stop the border short of the pane for no visible reason. */
459
+ .detail-meta { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 5px 14px; align-items: baseline; font-size: 11.5px; margin-bottom: 14px; padding-bottom: 13px; border-bottom: 1px solid var(--border); }
460
+ .meta-label { color: var(--subtext); font-size: 11.5px; white-space: nowrap; }
461
+ .meta-cells { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 14px; min-width: 0; }
462
+ .meta-value { min-width: 0; overflow-wrap: anywhere; cursor: pointer; }
463
+ .meta-value.mono { font-family: var(--mono-font); font-size: 11.5px; color: var(--subtext); }
464
+ /* The eight characters that identify the commit carry the contrast; the other
465
+ thirty-two are there to be copied, not read. */
466
+ .hash-lead { color: var(--text); font-weight: 600; }
467
+ .meta-name { color: var(--text); }
468
+ .meta-email { color: var(--subtext); margin-left: 6px; }
469
+ .meta-value:hover .hash-lead, .meta-value:hover .meta-name, .meta-value:hover .meta-email { color: var(--blue); }
470
+ .meta-value:hover { color: var(--blue); }
471
+ .meta-value.copied, .meta-value.copied .hash-lead, .meta-value.copied .meta-name, .meta-value.copied .meta-email { color: var(--green); }
472
+ .meta-value.copy-failed, .meta-value.copy-failed .hash-lead, .meta-value.copy-failed .meta-name, .meta-value.copy-failed .meta-email { color: var(--red); }
473
+ .meta-when { color: var(--subtext); font-variant-numeric: tabular-nums; }
474
+ .detail-subject { font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: -0.1px; }
475
+
476
+ /* The body. A paragraph its author wrapped at 72 columns is set as prose in the
477
+ UI font and reflowed to the pane it is actually in — verbatim monospace gave
478
+ a ragged half-filled column of the wrong width. A list or an aligned block is
479
+ not reflowable and keeps its breaks and its monospace; splitCommitBody
480
+ decides which is which. */
481
+ .detail-text { margin-top: 13px; }
482
+ .msg-p { font-size: 13px; line-height: 1.7; max-width: 68ch; }
483
+ .msg-pre { font-family: var(--mono-font); font-size: 12px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
484
+ /* The reset zeroes every margin, so the space between paragraphs is set here
485
+ rather than inherited from the browser's default for a p element. */
486
+ .msg-p + .msg-p, .msg-p + .msg-pre, .msg-pre + .msg-p, .msg-pre + .msg-pre { margin-top: 13px; }
487
+
488
+ /* One scrollbar per pane above the breakpoint. A long message and a long file
489
+ list are two lists of unrelated length, and scrolling the pair as one means
490
+ reaching the twentieth file by pushing the message off the screen. Below it
491
+ they are stacked, and two scrollers inside one short panel is a trap for a
492
+ thumb — so there the panel scrolls as a whole, as it always did. */
493
+ @media (min-width: 900px) {
494
+ .detail-panel.split { display: flex; flex-direction: column; overflow: hidden; }
495
+ .detail-grid.has-files { grid-template-columns: minmax(0, 1fr) minmax(260px, 38%); }
496
+ .detail-panel.split .detail-grid { flex: 1 1 auto; min-height: 0; }
497
+ .detail-panel.split .detail-grid > * { overflow-y: auto; overscroll-behavior: contain; min-height: 0; }
498
+ .detail-panel.split .detail-files { border-left: 1px solid var(--border); padding-top: 0; }
499
+ /* The pane's own top padding would sit above a sticky header, leaving a strip
500
+ for rows to scroll through; the header carries that space instead. */
501
+ .detail-panel.split .files-head { position: sticky; top: 0; background: var(--surface); z-index: 1; padding: 8px 0 3px; }
502
+ }
503
+
504
+ .file-list { margin-top: 8px; min-width: 0; }
505
+ .detail-files .file-list { margin-top: 0; }
506
+ .files-head { display: flex; align-items: center; gap: 8px; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--subtext); border-bottom: 1px solid var(--border); margin-bottom: 3px; }
507
+ .files-head .file-view-toggle { margin-bottom: 0; }
508
+ .files-total { margin-left: auto; font-family: var(--mono-font); font-size: 10px; letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; }
509
+ .files-total .add { color: var(--green); }
510
+ .files-total .del { color: var(--red); }
511
+
512
+ .file-item { display: flex; align-items: center; gap: 6px; padding: 2px 4px; border-radius: 4px; font-size: 11px; font-family: var(--mono-font); min-width: 0; }
513
+ .file-item.file-clickable { cursor: pointer; }
514
+ .file-item.file-clickable:hover { background: var(--surface-hover); }
515
+ /* Name first, then the directory it is in: the column is narrow, so what has
516
+ to survive the ellipsis is the file name. */
517
+ .file-item .file-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex-shrink: 0; max-width: 60%; }
518
+ /* The directory takes the slack, which is what leaves the stats and the row's
519
+ buttons together at the right edge instead of both claiming the same auto
520
+ margin and meeting somewhere in the middle. */
521
+ .file-item .file-dir { color: var(--subtle); font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }
340
522
  .file-status { display: inline-block; width: 14px; text-align: center; font-weight: 700; font-size: 10px; }
341
523
  .file-status-A { color: var(--green); }
342
524
  .file-status-M { color: var(--yellow); }
@@ -355,7 +537,7 @@ button:active { background: var(--surface); }
355
537
  .tree-dir-count { font-size: 11px; color: var(--subtle); }
356
538
 
357
539
  /* File actions */
358
- .file-actions { display: flex; gap: 2px; margin-left: auto; flex-shrink: 0; }
540
+ .file-actions { display: flex; gap: 2px; margin-left: 6px; flex-shrink: 0; }
359
541
  .file-action-btn { min-width: 24px; min-height: 24px; padding: 0 4px; border: none; background: transparent; cursor: pointer; border-radius: 4px; font-size: 12px; color: var(--subtext); display: flex; align-items: center; justify-content: center; }
360
542
  .file-action-btn:hover { background: var(--surface-hover); color: var(--text); }
361
543
  .file-action-btn[data-action="discard"]:hover { color: var(--red); }
@@ -401,12 +583,12 @@ button:active { background: var(--surface); }
401
583
  .remote-item { padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 12px; }
402
584
  .remote-item:last-child { border-bottom: none; }
403
585
  .remote-item .remote-name { font-weight: 600; margin-bottom: 2px; }
404
- .remote-item .remote-url { color: var(--subtext); font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; word-break: break-all; }
586
+ .remote-item .remote-url { color: var(--subtext); font-family: var(--mono-font); font-size: 11px; word-break: break-all; }
405
587
  .remote-actions { display: flex; gap: 4px; margin-top: 4px; }
406
588
  .add-remote-form { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
407
589
  .add-remote-form input { background: var(--bg); color: var(--text); border: 1px solid var(--border2); border-radius: 4px; padding: 4px 6px; font-size: 12px; }
408
590
  .issue-rule-row { display: flex; gap: 4px; align-items: center; margin-bottom: 4px; }
409
- .issue-rule-row input { flex: 1; background: var(--bg); color: var(--text); border: 1px solid var(--border2); border-radius: 4px; padding: 3px 6px; font-size: 11px; font-family: 'SF Mono', 'Fira Code', monospace; }
591
+ .issue-rule-row input { flex: 1; background: var(--bg); color: var(--text); border: 1px solid var(--border2); border-radius: 4px; padding: 3px 6px; font-size: 11px; font-family: var(--mono-font); }
410
592
  .issue-rule-row input.rule-error { border-color: var(--red); }
411
593
  .issue-rule-row .rule-remove { min-width: 24px; min-height: 24px; padding: 0; font-size: 14px; color: var(--red); border: none; }
412
594
  @media (max-width: 768px) { .settings-panel { width: 100%; } }
@@ -440,22 +622,42 @@ button:active { background: var(--surface); }
440
622
  .commit-row { height: 32px; }
441
623
  .ctx-item { padding: 8px 12px; min-height: 36px; }
442
624
  button { min-width: 32px; min-height: 32px; padding: 2px 6px; }
625
+ /* The one way out of the detail panel, and the only place 32 is not enough:
626
+ a phone has no Escape key, so a panel opened by a tap is closed by this
627
+ button or not at all. 10px each side takes the 24px box to 44, and the 32px
628
+ the rule above gives it to 52. */
629
+ .detail-close::before { content: ''; position: absolute; inset: -10px; }
443
630
  #app { flex-direction: column; }
444
631
  #toolbar { order: 10; border-bottom: none; border-top: 1px solid var(--border); padding: 2px 6px; }
445
632
  #toolbar button { font-size: 10px; }
446
633
  .branch-trigger { font-size: 10px !important; padding: 2px 6px !important; }
447
- #graph-container { order: 1; overflow-x: auto; overflow-y: auto; }
634
+ #graph-area { order: 1; }
635
+ #graph-container { overflow-x: auto; overflow-y: auto; }
448
636
  #find-bar { order: 0; }
449
637
  #status-bar { order: 9; }
450
- #commit-list-wrapper { min-width: 700px; }
451
- #graph-header { min-width: 700px; }
638
+ /* Enough for every column including the branch one, so a tablet scrolls the
639
+ table sideways rather than crushing it. */
640
+ #commit-list-wrapper { min-width: 880px; }
641
+ #graph-header { min-width: 880px; }
452
642
  .commit-row.header-row { min-height: 20px; }
453
643
  .detail-panel { order: 8; max-height: 35vh; }
454
644
  }
455
- /* Column hiding on very narrow non-touch containers (e.g. narrow desktop panel) */
456
- @media (max-width: 500px) and (pointer: fine) {
457
- .col-author, .col-hash { display: none; }
458
- .col-date { width: 60px; min-width: 60px; }
645
+ /* Phone layout: the graph and the message, with author/date/hash under the
646
+ subject. Six columns of fragments is not a list of commits, and 44px is the
647
+ minimum touch target this row has to be anyway. isNarrowLayout() in the
648
+ script uses this same 640px, because it decides where the ref badges go. */
649
+ @media (max-width: 640px) {
650
+ :root { --refs-col-w: 0px; }
651
+ /* The coarse-pointer rules above keep the whole table and scroll it
652
+ sideways, which is right for a tablet. A phone gets the stacked row
653
+ instead, so the minimum width that forces that scroll has to go. */
654
+ #commit-list-wrapper, #graph-header { min-width: 0; }
655
+ .commit-row { height: 44px; }
656
+ .commit-row.header-row { height: 24px; }
657
+ .col-refs, .col-changes, .col-author, .col-date, .col-hash { display: none; }
658
+ .col-message { gap: 1px; }
659
+ .msg-meta { display: block; font-size: 10px; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
660
+ .col-message .ref-badge { max-width: 90px; overflow: hidden; text-overflow: ellipsis; }
459
661
  }
460
662
  `;
461
663
  }
@@ -468,7 +670,10 @@ const NULL_VERTEX_ID = -1;
468
670
  const GRAPH_COLORS = ['#4ec9b0','#569cd6','#c586c0','#ce9178','#dcdcaa','#4fc1ff','#d7ba7d','#9cdcfe','#b5cea8','#d16969'];
469
671
  const graphConfig = {
470
672
  colours: GRAPH_COLORS,
471
- grid: { x: 16, y: 24, offsetX: 8, offsetY: 12, expandY: 60 },
673
+ // Wide enough apart that two adjacent lanes' author avatars do not touch,
674
+ // and tall enough that an 18px avatar is the row rather than a dot in it.
675
+ grid: { x: 22, y: 30, offsetX: 14, offsetY: 15, expandY: 60 },
676
+ nodeR: 9,
472
677
  style: 'rounded'
473
678
  };
474
679
 
@@ -495,6 +700,8 @@ const state = {
495
700
  loading: false,
496
701
  uncommitted: null,
497
702
  searchMatches: [],
703
+ /** hash -> {files, insertions, deletions}; arrives after the commits. */
704
+ stats: {},
498
705
  searchIndex: -1,
499
706
  settings: { ...DEFAULT_SETTINGS },
500
707
  userDetails: { name: '', email: '' },
@@ -549,6 +756,20 @@ document.getElementById('btn-stash').innerHTML = ICONS.archive + ' <span class="
549
756
  vscode.postMessage({ command: 'ready' });
550
757
 
551
758
  // --- Message handler ---
759
+ /*
760
+ * The row's layout is decided in JS as well as in CSS (the ref badges live in
761
+ * their own column on a wide screen and inline on a narrow one), so crossing
762
+ * the breakpoint has to rebuild the rows. Without this, rotating a phone would
763
+ * leave the badges in a column that CSS has just hidden.
764
+ */
765
+ let gWasNarrow = null;
766
+ window.addEventListener('resize', () => {
767
+ const narrow = isNarrowLayout();
768
+ if (gWasNarrow === narrow) return;
769
+ gWasNarrow = narrow;
770
+ if (state.commits.length > 0) renderCommitList();
771
+ });
772
+
552
773
  window.addEventListener('message', (event) => {
553
774
  const msg = event.data;
554
775
  switch (msg.command) {
@@ -572,14 +793,31 @@ window.addEventListener('message', (event) => {
572
793
  state.commits = state.commits.concat(msg.data);
573
794
  } else {
574
795
  state.commits = msg.data;
796
+ // Not an append means the list was replaced — a branch change, a
797
+ // refresh. Hashes are unique so keeping the old numbers would not be
798
+ // wrong, but nothing would ever read them again and nothing would ever
799
+ // drop them either: a long session switching branches grows a map of
800
+ // every commit it has ever looked at.
801
+ state.stats = {};
575
802
  }
576
803
  renderCommitList();
577
804
  updateStatus();
578
805
  state.loading = false;
579
806
  document.getElementById('loading').classList.add('hidden');
580
807
  break;
808
+ case 'loadCommitStats':
809
+ Object.assign(state.stats, msg.data);
810
+ applyCommitStats();
811
+ break;
581
812
  case 'commitDetails':
582
- renderDetailPanel(msg.data);
813
+ // The answer has to still be the question. Arrow-keying down the list
814
+ // spawns one "git show" per row with nothing cancelling the last, so they
815
+ // come back in whatever order they finish: click A then B quickly and the
816
+ // panel could show A while B is highlighted, and deselecting A while its
817
+ // request was in flight popped the panel open again for a commit that is
818
+ // no longer selected. (No backticks in here: this whole script is a
819
+ // template literal, and one would end it hundreds of lines early.)
820
+ if (msg.data && msg.data.hash === state.selectedCommit) renderDetailPanel(msg.data);
583
821
  break;
584
822
  case 'refresh':
585
823
  state.commits = msg.data;
@@ -705,6 +943,22 @@ document.getElementById('detail-panel').addEventListener('contextmenu', (e) => {
705
943
 
706
944
  // --- File click delegation (opens diff tab) ---
707
945
  document.getElementById('detail-panel').addEventListener('click', (e) => {
946
+ // The header's dismiss.
947
+ if (e.target.closest('.detail-close')) { e.stopPropagation(); closeDetailPanel(); return; }
948
+ // Metadata values. The clipboard write is silent, so the thing clicked says
949
+ // it happened.
950
+ const copySource = e.target.closest('[data-copy]');
951
+ if (copySource) {
952
+ e.stopPropagation();
953
+ // The green flash is the only evidence the user gets, so it waits for the
954
+ // answer rather than assuming one. A failure says so instead of lying.
955
+ copyText(copySource.dataset.copy).then((ok) => {
956
+ const cls = ok ? 'copied' : 'copy-failed';
957
+ copySource.classList.add(cls);
958
+ setTimeout(() => copySource.classList.remove(cls), 900);
959
+ });
960
+ return;
961
+ }
708
962
  // File-level action buttons (stage/unstage/discard/open)
709
963
  const actionBtn = e.target.closest('.file-action-btn');
710
964
  if (actionBtn) {
@@ -1387,9 +1641,11 @@ class GBranch {
1387
1641
  }
1388
1642
 
1389
1643
  class GVertex {
1390
- constructor(id, isStash) {
1644
+ constructor(id, isStash, author) {
1391
1645
  this.id = id;
1392
1646
  this.isStash = isStash;
1647
+ /** {name, email} of the commit's author, or null for a row that has none. */
1648
+ this._author = author || null;
1393
1649
  this._x = 0;
1394
1650
  this._children = [];
1395
1651
  this._parents = [];
@@ -1432,6 +1688,16 @@ class GVertex {
1432
1688
  setNotCommitted() { this._isCommitted = false; }
1433
1689
  setCurrent() { this._isCurrent = true; }
1434
1690
 
1691
+ /*
1692
+ * The node is the author's avatar, ringed in the lane's colour — which is
1693
+ * what makes a graph readable as *who* rather than as a column of identical
1694
+ * dots. Initials, never a fetched image: an avatar service would mean handing
1695
+ * every committer's email address to a third party, which a self-hosted tool
1696
+ * must not do quietly.
1697
+ *
1698
+ * Rows with no author — uncommitted changes, a stash — keep a plain dot,
1699
+ * because inventing initials for them would be a lie.
1700
+ */
1435
1701
  draw(svg, config, expandOffset, overListener, outListener) {
1436
1702
  if (this._onBranch === null) return;
1437
1703
  const STASH_COLOR = '#808080';
@@ -1439,20 +1705,32 @@ class GVertex {
1439
1705
  : this._isCommitted ? config.colours[this._onBranch.getColour() % config.colours.length] : '#808080';
1440
1706
  const cx = (this._x * config.grid.x + config.grid.offsetX).toString();
1441
1707
  const cy = (this.id * config.grid.y + config.grid.offsetY + (expandOffset ? config.grid.expandY : 0)).toString();
1708
+ const named = this._isCommitted && !this.isStash && !!this._author && !!this._author.name;
1709
+ const r = named ? (config.nodeR || 9) : 4.5;
1442
1710
 
1443
1711
  const circle = document.createElementNS(SVG_NS, 'circle');
1444
1712
  circle.dataset.id = this.id.toString();
1713
+ circle.dataset.r = r.toString();
1445
1714
  circle.setAttribute('cx', cx);
1446
1715
  circle.setAttribute('cy', cy);
1447
- circle.setAttribute('r', '4');
1448
- if (this._isCurrent) {
1449
- circle.setAttribute('class', 'graphCurrent');
1450
- circle.setAttribute('stroke', colour);
1451
- } else {
1452
- circle.setAttribute('fill', colour);
1453
- }
1716
+ circle.setAttribute('r', r.toString());
1717
+ circle.setAttribute('stroke', colour);
1718
+ circle.setAttribute('stroke-width', named ? '2' : '0');
1719
+ circle.setAttribute('fill', named ? authorColor(this._author.email || this._author.name) : colour);
1720
+ if (this._isCurrent) circle.setAttribute('class', 'graphCurrent');
1454
1721
  svg.appendChild(circle);
1455
1722
 
1723
+ if (named) {
1724
+ const label = document.createElementNS(SVG_NS, 'text');
1725
+ label.setAttribute('class', 'node-initials');
1726
+ label.setAttribute('x', cx);
1727
+ label.setAttribute('y', cy);
1728
+ label.setAttribute('text-anchor', 'middle');
1729
+ label.setAttribute('dominant-baseline', 'central');
1730
+ label.textContent = authorInitials(this._author.name);
1731
+ svg.appendChild(label);
1732
+ }
1733
+
1456
1734
  circle.addEventListener('mouseover', overListener);
1457
1735
  circle.addEventListener('mouseout', outListener);
1458
1736
  }
@@ -1469,7 +1747,7 @@ function graphLoadCommits(commits) {
1469
1747
  const lookup = {};
1470
1748
  for (let i = 0; i < commits.length; i++) {
1471
1749
  lookup[commits[i].hash] = i;
1472
- gVertices.push(new GVertex(i, !!commits[i]._isStash));
1750
+ gVertices.push(new GVertex(i, !!commits[i]._isStash, { name: commits[i].author, email: commits[i].authorEmail }));
1473
1751
  }
1474
1752
  gCommitLookup = lookup;
1475
1753
 
@@ -1593,7 +1871,7 @@ function graphVertexOver(e) {
1593
1871
  if (id >= 0 && id < gVertices.length) {
1594
1872
  const rows = document.querySelectorAll('.commit-row:not(.header-row)');
1595
1873
  if (rows[id]) rows[id].classList.add('graph-hover');
1596
- e.target.setAttribute('r', '5');
1874
+ e.target.setAttribute('r', (Number(e.target.dataset.r || 4.5) + 1.5).toString());
1597
1875
  }
1598
1876
  }
1599
1877
  function graphVertexOut(e) {
@@ -1602,7 +1880,7 @@ function graphVertexOut(e) {
1602
1880
  if (id >= 0) {
1603
1881
  const rows = document.querySelectorAll('.commit-row:not(.header-row)');
1604
1882
  if (rows[id]) rows[id].classList.remove('graph-hover');
1605
- e.target.setAttribute('r', '4');
1883
+ e.target.setAttribute('r', (e.target.dataset.r || '4.5'));
1606
1884
  }
1607
1885
  }
1608
1886
 
@@ -1663,6 +1941,18 @@ function getDisplayCommits() {
1663
1941
  return commits;
1664
1942
  }
1665
1943
 
1944
+ /*
1945
+ * Below this width the row carries the graph and the message only, with the
1946
+ * author, date and hash on a second line under the subject: six columns of
1947
+ * fragments on a phone is not a list of commits, and PPM's layout rules ask for
1948
+ * one column there. The value matches the max-width:640px block in the CSS —
1949
+ * both have to agree, because the JS decides where the ref badges are put and
1950
+ * the CSS decides which columns exist.
1951
+ */
1952
+ function isNarrowLayout() {
1953
+ return typeof window.matchMedia === 'function' && window.matchMedia('(max-width: 640px)').matches;
1954
+ }
1955
+
1666
1956
  function renderCommitList() {
1667
1957
  const container = document.getElementById('commit-list');
1668
1958
  container.innerHTML = '';
@@ -1682,11 +1972,17 @@ function renderCommitList() {
1682
1972
  row.className = 'commit-row' + (isVirtual ? ' virtual' : '') + (isStash ? ' stash-row' : '');
1683
1973
  row.dataset.hash = commit.hash;
1684
1974
 
1975
+ // Branch / tag column. Below 640px there is no room for it, so the badges
1976
+ // go inline ahead of the subject instead — one home or the other, never
1977
+ // both, or the second copy's context menu would act on a hidden badge.
1978
+ const narrow = isNarrowLayout();
1979
+ const refsCol = document.createElement('div');
1980
+ refsCol.className = 'col-refs';
1981
+
1685
1982
  // Graph spacer column (SVG overlays this area)
1686
1983
  const graphCol = document.createElement('div');
1687
1984
  graphCol.className = 'col-graph';
1688
1985
 
1689
- // Message column with ref badges
1690
1986
  const msgCol = document.createElement('div');
1691
1987
  msgCol.className = 'col-message';
1692
1988
  let badges = '';
@@ -1721,10 +2017,23 @@ function renderCommitList() {
1721
2017
  badges += '<span class="ref-badge ref-' + ref.type + '" data-ref="' + escHtml(ref.name) + '">' + syncIcon + escHtml(ref.name) + '</span>';
1722
2018
  });
1723
2019
  }
1724
- msgCol.innerHTML = badges + formatCommitMessage(commit.message);
2020
+ if (!narrow) refsCol.innerHTML = badges;
2021
+ const meta = isVirtual || isStash ? ''
2022
+ : '<span class="msg-meta">' + escHtml(commit.author) + ' \u00b7 '
2023
+ + escHtml(formatDate(commit.commitDate)) + ' \u00b7 ' + escHtml(commit.hash.substring(0, 7))
2024
+ + '</span>';
2025
+ msgCol.innerHTML = '<span class="msg-subject">' + (narrow ? badges : '')
2026
+ + formatCommitMessage(commit.message) + '</span>' + meta;
2027
+
2028
+ // The lane's colour, for the tick at the start of the message.
2029
+ const vertex = gVertices[idx];
2030
+ if (vertex && !isVirtual && !isStash) {
2031
+ row.style.setProperty('--lane', GRAPH_COLORS[vertex.getColour() % GRAPH_COLORS.length]);
2032
+ }
1725
2033
 
1726
2034
  // Attach context menu and double-click to ref badges
1727
- msgCol.querySelectorAll('.ref-badge').forEach(badge => {
2035
+ row.appendChild(refsCol);
2036
+ row.querySelectorAll('.ref-badge').forEach(badge => {
1728
2037
  const refName = badge.dataset.ref || badge.textContent.trim();
1729
2038
  const refType = badge.className.includes('ref-head') ? 'head'
1730
2039
  : badge.className.includes('ref-remote') ? 'remote'
@@ -1751,13 +2060,18 @@ function renderCommitList() {
1751
2060
  }
1752
2061
  });
1753
2062
 
2063
+ const changesCol = document.createElement('div');
2064
+ changesCol.className = 'col-changes';
2065
+ if (!isVirtual && !isStash) fillChangesCell(changesCol, state.stats[commit.hash]);
2066
+
1754
2067
  const authorCol = document.createElement('div');
1755
2068
  authorCol.className = 'col-author';
1756
2069
  if (isVirtual || isStash) {
1757
2070
  authorCol.textContent = '';
1758
2071
  } else {
1759
- authorCol.innerHTML = avatarHtml(commit.author, commit.authorEmail)
1760
- + '<span class="author-name">' + escHtml(commit.author) + '</span>';
2072
+ // No avatar here any more — it is the graph node now, and twice on one
2073
+ // row was noise.
2074
+ authorCol.textContent = commit.author;
1761
2075
  authorCol.addEventListener('mouseenter', () => showAuthorCard(authorCol, commit));
1762
2076
  authorCol.addEventListener('mouseleave', hideAuthorCard);
1763
2077
  }
@@ -1765,6 +2079,9 @@ function renderCommitList() {
1765
2079
  const dateCol = document.createElement('div');
1766
2080
  dateCol.className = 'col-date';
1767
2081
  dateCol.textContent = isVirtual ? 'now' : isStash ? '' : formatDate(commit.commitDate);
2082
+ // The column is relative by default, so the exact moment goes in the title
2083
+ // rather than in a wider column.
2084
+ if (!isVirtual && !isStash) dateCol.title = new Date(commit.commitDate * 1000).toLocaleString();
1768
2085
 
1769
2086
  const hashCol = document.createElement('div');
1770
2087
  hashCol.className = 'col-hash';
@@ -1772,6 +2089,7 @@ function renderCommitList() {
1772
2089
 
1773
2090
  row.appendChild(graphCol);
1774
2091
  row.appendChild(msgCol);
2092
+ row.appendChild(changesCol);
1775
2093
  row.appendChild(authorCol);
1776
2094
  row.appendChild(dateCol);
1777
2095
  row.appendChild(hashCol);
@@ -1803,6 +2121,68 @@ function renderCommitList() {
1803
2121
  });
1804
2122
 
1805
2123
  graphRender(-1);
2124
+ renderScrollMarkers();
2125
+ }
2126
+
2127
+ /*
2128
+ * A merge commit has no diffstat — that is git's default, not a failure — so an
2129
+ * absent entry leaves the cell empty rather than claiming it changed nothing.
2130
+ */
2131
+ function fillChangesCell(cell, stat) {
2132
+ cell.textContent = '';
2133
+ if (!stat) return;
2134
+ if (stat.insertions > 0) {
2135
+ const add = document.createElement('span');
2136
+ add.className = 'ch-add';
2137
+ add.textContent = '+' + stat.insertions;
2138
+ cell.appendChild(add);
2139
+ }
2140
+ if (stat.deletions > 0) {
2141
+ const del = document.createElement('span');
2142
+ del.className = 'ch-del';
2143
+ del.textContent = '-' + stat.deletions;
2144
+ cell.appendChild(del);
2145
+ }
2146
+ cell.title = stat.files + (stat.files === 1 ? ' file changed' : ' files changed');
2147
+ }
2148
+
2149
+ /** Fill the column in place: the rows are already drawn, and rebuilding them
2150
+ * would throw away the scroll position and the open detail panel. */
2151
+ function applyCommitStats() {
2152
+ document.querySelectorAll('#commit-list .commit-row').forEach((row) => {
2153
+ const cell = row.querySelector('.col-changes');
2154
+ const hash = row.dataset.hash;
2155
+ if (cell && hash && hash !== 'uncommitted') fillChangesCell(cell, state.stats[hash]);
2156
+ });
2157
+ }
2158
+
2159
+ /*
2160
+ * One tick per interesting row, positioned by its index in the whole loaded
2161
+ * history rather than by pixels — the rows are a uniform height, so the two
2162
+ * agree, and an index needs no measuring and survives a resize.
2163
+ *
2164
+ * Search matches are drawn first so that the checked-out and selected ticks sit
2165
+ * on top of them when they land on the same row.
2166
+ */
2167
+ function renderScrollMarkers() {
2168
+ const host = document.getElementById('scroll-markers');
2169
+ if (!host) return;
2170
+ host.innerHTML = '';
2171
+ const commits = getDisplayCommits();
2172
+ if (commits.length === 0) return;
2173
+
2174
+ const mark = (idx, kind) => {
2175
+ const el = document.createElement('div');
2176
+ el.className = 'scroll-marker sm-' + kind;
2177
+ el.style.top = ((idx + 0.5) / commits.length * 100) + '%';
2178
+ host.appendChild(el);
2179
+ };
2180
+
2181
+ for (let i = 0; i < state.searchMatches.length; i++) mark(state.searchMatches[i], 'search');
2182
+ for (let i = 0; i < commits.length; i++) {
2183
+ if (commits[i].hash === state.head) mark(i, 'head');
2184
+ if (commits[i].hash === state.selectedCommit) mark(i, 'selected');
2185
+ }
1806
2186
  }
1807
2187
 
1808
2188
  function selectCommit(hash) {
@@ -1813,6 +2193,7 @@ function selectCommit(hash) {
1813
2193
  state.selectedCommit = null;
1814
2194
  state.expandedCommit = null;
1815
2195
  document.getElementById('detail-panel').classList.add('hidden');
2196
+ renderScrollMarkers();
1816
2197
  return;
1817
2198
  }
1818
2199
 
@@ -1820,6 +2201,7 @@ function selectCommit(hash) {
1820
2201
  state.expandedCommit = hash;
1821
2202
  const row = document.querySelector('[data-hash="' + CSS.escape(hash) + '"]');
1822
2203
  if (row) row.classList.add('selected');
2204
+ renderScrollMarkers();
1823
2205
 
1824
2206
  if (hash === 'uncommitted') {
1825
2207
  renderUncommittedDetail();
@@ -1828,6 +2210,17 @@ function selectCommit(hash) {
1828
2210
  vscode.postMessage({ command: 'requestCommitDetails', hash });
1829
2211
  }
1830
2212
 
2213
+ /* One dismiss for the close button and for Escape. They have to leave exactly
2214
+ the same state behind: a row still marked selected, or a marker still in
2215
+ the scroll bar, outlives the panel that explained it. */
2216
+ function closeDetailPanel() {
2217
+ state.selectedCommit = null;
2218
+ state.expandedCommit = null;
2219
+ document.getElementById('detail-panel').classList.add('hidden');
2220
+ document.querySelectorAll('.commit-row.selected').forEach(el => el.classList.remove('selected'));
2221
+ renderScrollMarkers();
2222
+ }
2223
+
1831
2224
  // --- File tree helpers ---
1832
2225
  function buildFileTree(files) {
1833
2226
  const root = { name: '', children: {}, files: [] };
@@ -1862,7 +2255,7 @@ function renderFileTree(node, depth, hash, parentHash, section) {
1862
2255
  const sortedFiles = [...node.files].sort((a, b) => a.fileName.localeCompare(b.fileName));
1863
2256
  for (const f of sortedFiles) {
1864
2257
  html += '<div class="file-item file-clickable" style="padding-left:' + (depth * 16) + 'px" ';
1865
- html += 'data-path="' + escHtml(f.path) + '" data-hash="' + escHtml(hash) + '" data-parent="' + escHtml(parentHash) + '">';
2258
+ html += 'data-path="' + escHtml(f.path) + '" data-hash="' + escHtml(hash) + '" data-parent="' + escHtml(parentHash || '') + '">';
1866
2259
  html += '<span class="file-status file-status-' + escHtml(f.status) + '">' + escHtml(f.status) + '</span>';
1867
2260
  html += '<span class="file-name">' + escHtml(f.fileName) + '</span>';
1868
2261
  if (f.additions > 0 || f.deletions > 0) {
@@ -1877,14 +2270,27 @@ function renderFileTree(node, depth, hash, parentHash, section) {
1877
2270
  return html;
1878
2271
  }
1879
2272
 
2273
+ /* Split so the file name can be shown before the directory it sits in: the
2274
+ list lives in a narrow column, and truncating from the end has to eat the
2275
+ path, not the name. */
2276
+ function basename(path) {
2277
+ const i = String(path).lastIndexOf('/');
2278
+ return i === -1 ? String(path) : String(path).slice(i + 1);
2279
+ }
2280
+ function dirname(path) {
2281
+ const i = String(path).lastIndexOf('/');
2282
+ return i === -1 ? '' : String(path).slice(0, i);
2283
+ }
2284
+
1880
2285
  function renderFileListHtml(files, hash, parentHash, section) {
1881
2286
  if (state.fileViewMode === 'tree') {
1882
2287
  return renderFileTree(buildFileTree(files), 0, hash, parentHash, section);
1883
2288
  }
1884
2289
  return files.map(f =>
1885
- '<div class="file-item file-clickable" data-path="' + escHtml(f.path) + '" data-hash="' + escHtml(hash) + '" data-parent="' + escHtml(parentHash || '') + '">' +
2290
+ '<div class="file-item file-clickable" title="' + escHtml(f.path) + '" data-path="' + escHtml(f.path) + '" data-hash="' + escHtml(hash) + '" data-parent="' + escHtml(parentHash || '') + '">' +
1886
2291
  '<span class="file-status file-status-' + escHtml(f.status) + '">' + escHtml(f.status) + '</span>' +
1887
- '<span class="file-name">' + escHtml(f.path) + '</span>' +
2292
+ '<span class="file-name">' + escHtml(basename(f.path)) + '</span>' +
2293
+ (dirname(f.path) ? '<span class="file-dir">' + escHtml(dirname(f.path)) + '</span>' : '') +
1888
2294
  '<span class="file-stat">' +
1889
2295
  (f.additions > 0 ? '<span class="add">+' + f.additions + '</span> ' : '') +
1890
2296
  (f.deletions > 0 ? '<span class="del">-' + f.deletions + '</span>' : '') +
@@ -1894,6 +2300,15 @@ function renderFileListHtml(files, hash, parentHash, section) {
1894
2300
  ).join('');
1895
2301
  }
1896
2302
 
2303
+ /* A cap that does not say it capped states a wrong number as a fact: a commit
2304
+ touching ten thousand files rendered as "500 files changed", which is a
2305
+ sentence the panel has no evidence for. The message cap already spells its
2306
+ own truncation out, so this says it the same way. */
2307
+ function fileCountLabel(shown, omitted) {
2308
+ return shown + (shown === 1 ? ' file' : ' files') + ' changed'
2309
+ + (omitted > 0 ? ' [… ' + omitted + ' more]' : '');
2310
+ }
2311
+
1897
2312
  function renderFileActions(file, section) {
1898
2313
  let html = '<span class="file-actions">';
1899
2314
  if (section === 'unstaged') {
@@ -1917,9 +2332,10 @@ function fileViewToggleHtml() {
1917
2332
  function renderUncommittedDetail() {
1918
2333
  const panel = document.getElementById('detail-panel');
1919
2334
  panel.classList.remove('hidden');
2335
+ panel.classList.remove('split');
1920
2336
  const u = state.uncommitted;
1921
2337
  if (!u) { panel.classList.add('hidden'); return; }
1922
- let html = '<h3>Uncommitted Changes</h3>';
2338
+ let html = '<div class="detail-pad"><h3>Uncommitted Changes</h3>';
1923
2339
  const hasFiles = u.staged.length > 0 || u.unstaged.length > 0 || (u.conflicted && u.conflicted.length > 0);
1924
2340
  if (hasFiles) {
1925
2341
  html += fileViewToggleHtml();
@@ -1957,7 +2373,7 @@ function renderUncommittedDetail() {
1957
2373
  html += '<div class="commit-section">';
1958
2374
  html += '<textarea id="commit-message" placeholder="Commit message..." rows="3"></textarea>';
1959
2375
  html += '<div class="commit-actions"><button id="btn-commit" class="btn-sm btn-commit" disabled>Commit</button></div>';
1960
- html += '</div>';
2376
+ html += '</div></div>';
1961
2377
  panel.innerHTML = html;
1962
2378
  wireCommitControls();
1963
2379
  }
@@ -1983,30 +2399,129 @@ function wireCommitControls() {
1983
2399
  }
1984
2400
 
1985
2401
  // --- Detail panel ---
2402
+
2403
+ /* A value you can read in full and click to copy. Anything with a data-copy
2404
+ attribute is handled by the panel's one click delegate. */
2405
+ function copyable(inner, text, cls) {
2406
+ return '<span class="meta-value ' + cls + '" data-copy="' + escHtml(text)
2407
+ + '" title="Click to copy">' + inner + '</span>';
2408
+ }
2409
+
2410
+ /* Forty hex characters in one run is not text anyone reads. The eight that
2411
+ identify the commit carry the contrast and the other thirty-two go quiet —
2412
+ the whole thing is still there, and still what a click copies. */
2413
+ function hashCell(hash) {
2414
+ const lead = String(hash).slice(0, 8);
2415
+ const tail = String(hash).slice(8);
2416
+ return copyable('<span class="hash-lead">' + escHtml(lead) + '</span>' + escHtml(tail), hash, 'mono');
2417
+ }
2418
+
2419
+ /* Name and email are two things, so they are told apart by weight rather than
2420
+ by angle brackets. What a click copies is still the canonical form, which is
2421
+ what git itself wants back. */
2422
+ function personCell(name, email) {
2423
+ return copyable(
2424
+ '<span class="meta-name">' + escHtml(name) + '</span><span class="meta-email">' + escHtml(email) + '</span>',
2425
+ name + ' <' + email + '>',
2426
+ 'person',
2427
+ );
2428
+ }
2429
+
2430
+ /* The timezone is part of the answer: a commit stamped 09:13 means nothing
2431
+ without knowing whose morning that was. Spelled out component by component
2432
+ because dateStyle and timeStyle may not be combined with any other option —
2433
+ asking for those plus timeZoneName is a TypeError, and behind a catch it
2434
+ looks exactly like a locale that has no timezone name to give. */
2435
+ const WHEN_FORMAT = {
2436
+ year: 'numeric', month: 'short', day: 'numeric',
2437
+ hour: 'numeric', minute: '2-digit', second: '2-digit',
2438
+ timeZoneName: 'short',
2439
+ };
2440
+ function whenCell(ts) {
2441
+ const when = new Date(ts * 1000);
2442
+ return '<span class="meta-when">' + escHtml(when.toLocaleString(undefined, WHEN_FORMAT)) + '</span>';
2443
+ }
2444
+
2445
+ /* One label, one value, every row the same shape. The date used to ride along
2446
+ in a third column so that the two dates lined up — which put it a name's
2447
+ width away from the name it belonged to, and stranded it against the far edge
2448
+ of the pane on any row that had one. A date is a field like the others. */
2449
+ function metaRow(label, cells) {
2450
+ return '<div class="meta-label">' + escHtml(label) + '</div>'
2451
+ + '<div class="meta-cells">' + cells.join('') + '</div>';
2452
+ }
2453
+
1986
2454
  function renderDetailPanel(detail) {
1987
2455
  state._lastDetail = detail;
1988
2456
  const panel = document.getElementById('detail-panel');
1989
2457
  panel.classList.remove('hidden');
1990
2458
 
1991
- let html = '<h3>Commit Details</h3>';
1992
- html += '<div class="detail-field"><span class="label">Hash:</span> ' + escHtml(detail.hash) + '</div>';
1993
- html += '<div class="detail-field"><span class="label">Author:</span> ' + escHtml(detail.author) + ' &lt;' + escHtml(detail.authorEmail) + '&gt;</div>';
1994
- html += '<div class="detail-field"><span class="label">Date:</span> ' + new Date(detail.authorDate * 1000).toLocaleString() + '</div>';
2459
+ // Who and when, then the way out: the glanceable half.
2460
+ let head = '<div class="detail-head">' + avatarHtml(detail.author, detail.authorEmail);
2461
+ head += '<div class="detail-who">';
2462
+ head += '<span class="detail-author" title="' + escHtml(detail.authorEmail) + '">' + escHtml(detail.author) + '</span>';
2463
+ head += '<span class="detail-when" title="' + escHtml(new Date(detail.authorDate * 1000).toLocaleString()) + '">committed ' + escHtml(formatDate(detail.authorDate)) + '</span>';
1995
2464
  if (detail.committer !== detail.author) {
1996
- html += '<div class="detail-field"><span class="label">Committer:</span> ' + escHtml(detail.committer) + ' &lt;' + escHtml(detail.committerEmail) + '&gt;</div>';
1997
- }
2465
+ head += '<span class="detail-when" title="' + escHtml(detail.committerEmail) + '">via ' + escHtml(detail.committer) + '</span>';
2466
+ }
2467
+ head += '</div><div class="detail-head-actions">';
2468
+ head += '<button class="detail-close" title="Close (Esc)" aria-label="Close commit details">' + ICONS.x + '</button>';
2469
+ head += '</div></div>';
2470
+
2471
+ // Then the values themselves, which since the chips gave up the header is
2472
+ // where both hashes are. Every row is always here, in
2473
+ // the same order, even when the committer repeats the author: a field that
2474
+ // comes and goes cannot be found by muscle memory, and the two dates only
2475
+ // mean anything next to each other — which is what a rebase or an amend does
2476
+ // to them, and the reason both are separate fields rather than one date.
2477
+ let meta = '<div class="detail-meta">';
2478
+ meta += metaRow('Commit', [hashCell(detail.hash)]);
1998
2479
  if (detail.parents.length > 0) {
1999
- html += '<div class="detail-field"><span class="label">Parents:</span> ' + detail.parents.map(p => escHtml(p.substring(0, 7))).join(', ') + '</div>';
2480
+ meta += metaRow(detail.parents.length > 1 ? 'Parents' : 'Parent', detail.parents.map(hashCell));
2481
+ }
2482
+ meta += metaRow('Author', [personCell(detail.author, detail.authorEmail)]);
2483
+ meta += metaRow('Author date', [whenCell(detail.authorDate)]);
2484
+ meta += metaRow('Committer', [personCell(detail.committer, detail.committerEmail)]);
2485
+ meta += metaRow('Commit date', [whenCell(detail.commitDate)]);
2486
+ meta += '</div>';
2487
+
2488
+ // The subject carries the weight; the body keeps the author's own wrapping.
2489
+ const message = String(detail.message || '');
2490
+ const firstBreak = message.indexOf('\\n');
2491
+ const subject = firstBreak === -1 ? message : message.slice(0, firstBreak);
2492
+ const body = firstBreak === -1 ? '' : message.slice(firstBreak + 1).replace(/^\\n+/, '').replace(/\\s+$/, '');
2493
+ let left = '<div class="detail-msg">' + meta + '<div class="detail-subject">' + formatCommitMessage(subject) + '</div>';
2494
+ if (body) {
2495
+ // A paragraph the author wrapped at 72 columns is reflowed to the pane it
2496
+ // is actually in; a list or an aligned block keeps every break it had.
2497
+ let blocks = '';
2498
+ for (const block of splitCommitBody(body)) {
2499
+ blocks += block.kind === 'prose'
2500
+ ? '<p class="msg-p">' + formatCommitMessage(block.text) + '</p>'
2501
+ : '<pre class="msg-pre">' + formatCommitMessage(block.text) + '</pre>';
2502
+ }
2503
+ left += '<div class="detail-text">' + blocks + '</div>';
2000
2504
  }
2001
- html += '<div class="detail-message">' + escHtml(detail.message) + '</div>';
2505
+ left += '</div>';
2002
2506
 
2507
+ let right = '';
2003
2508
  if (detail.fileChanges && detail.fileChanges.length > 0) {
2004
- html += '<div class="file-list">' + fileViewToggleHtml() + '<strong>Files changed (' + detail.fileChanges.length + '):</strong>';
2005
- html += renderFileListHtml(detail.fileChanges, detail.hash, detail.parents[0] || '');
2006
- html += '</div>';
2509
+ let added = 0, removed = 0;
2510
+ for (const f of detail.fileChanges) { added += f.additions || 0; removed += f.deletions || 0; }
2511
+ right = '<div class="detail-files"><div class="file-list"><div class="files-head">'
2512
+ + '<span>' + fileCountLabel(detail.fileChanges.length, detail.filesOmitted || 0) + '</span>'
2513
+ + '<span class="files-total">'
2514
+ + (added > 0 ? '<span class="add">+' + added + '</span> ' : '')
2515
+ + (removed > 0 ? '<span class="del">-' + removed + '</span>' : '')
2516
+ + '</span>' + fileViewToggleHtml() + '</div>'
2517
+ + renderFileListHtml(detail.fileChanges, detail.hash, detail.parents[0] || '')
2518
+ + '</div></div>';
2007
2519
  }
2008
2520
 
2009
- panel.innerHTML = html;
2521
+ // The split panes each own a scrollbar, which means the panel must stop
2522
+ // owning one — and must give it back for any other view written into it.
2523
+ panel.classList.toggle('split', !!right);
2524
+ panel.innerHTML = head + '<div class="detail-grid' + (right ? ' has-files' : '') + '">' + left + right + '</div>';
2010
2525
  }
2011
2526
 
2012
2527
  // --- Context menu ---
@@ -2308,8 +2823,44 @@ function promptResetMode(hash) {
2308
2823
  });
2309
2824
  }
2310
2825
 
2311
- function copyText(text) {
2312
- navigator.clipboard.writeText(text).catch(() => {});
2826
+ /* A panel is mounted sandbox="allow-scripts" with no allow-same-origin, so it
2827
+ runs at an OPAQUE ORIGIN — and the default Permissions Policy allowlist for
2828
+ clipboard-write is "self", which an opaque origin never matches. Measured in
2829
+ Chromium with the iframe focused and a real click: writeText rejects with
2830
+ NotAllowedError ("blocked because of a permissions policy"), while a textarea
2831
+ plus execCommand('copy') returns true and the text really does land on the
2832
+ system clipboard (pasted it back out to check). So every click-to-copy value
2833
+ in every panel had been copying nothing, and saying that it had.
2834
+
2835
+ The modern call is still tried first, for the day the policy changes. The
2836
+ textarea has to stay RENDERED — display:none or visibility:hidden give an
2837
+ empty selection and execCommand then copies nothing while still returning
2838
+ true — and setSelectionRange is what actually selects on iOS. */
2839
+ async function copyText(text) {
2840
+ if (navigator.clipboard && navigator.clipboard.writeText) {
2841
+ try {
2842
+ await navigator.clipboard.writeText(text);
2843
+ return true;
2844
+ } catch (e) { /* fall through */ }
2845
+ }
2846
+ const ta = document.createElement('textarea');
2847
+ ta.value = String(text == null ? '' : text);
2848
+ ta.setAttribute('readonly', '');
2849
+ ta.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;padding:0;border:none;opacity:0;';
2850
+ document.body.appendChild(ta);
2851
+ const selection = document.getSelection();
2852
+ const previous = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
2853
+ try {
2854
+ ta.focus();
2855
+ ta.select();
2856
+ ta.setSelectionRange(0, ta.value.length);
2857
+ return document.execCommand('copy');
2858
+ } catch (e) {
2859
+ return false;
2860
+ } finally {
2861
+ ta.remove();
2862
+ if (selection && previous) { selection.removeAllRanges(); selection.addRange(previous); }
2863
+ }
2313
2864
  }
2314
2865
 
2315
2866
  // --- Dialog system ---
@@ -2390,37 +2941,7 @@ function setupLongPress(el, callback) {
2390
2941
  }
2391
2942
 
2392
2943
  // --- Text formatter (URLs, issues, commit hashes) ---
2393
- function formatCommitMessage(msg) {
2394
- let safe = escHtml(msg);
2395
- // Apply issue linking rules from settings
2396
- const rules = state.settings.issueLinkingRules || [];
2397
- for (const rule of rules) {
2398
- if (!rule.pattern) continue;
2399
- if (rule.pattern.length > 200) continue; // ReDoS guard
2400
- try {
2401
- const re = new RegExp(rule.pattern, 'g');
2402
- if (rule.url) {
2403
- safe = safe.replace(re, function(match) {
2404
- let href = rule.url;
2405
- for (let i = 1; i < arguments.length - 2; i++) {
2406
- if (typeof arguments[i] === 'string') href = href.split('$' + i).join(arguments[i]);
2407
- }
2408
- return '<a class="commit-link" href="' + escHtml(href) + '" target="_blank" title="' + escHtml(href) + '">' + match + '</a>';
2409
- });
2410
- } else {
2411
- safe = safe.replace(re, '<span class="commit-link" title="$&">$&</span>');
2412
- }
2413
- } catch (e) { /* invalid regex — skip */ }
2414
- }
2415
- // Short commit hashes
2416
- safe = safe.replace(/\\b([0-9a-f]{7,40})\\b/g, '<span class="commit-link" title="$1">$1</span>');
2417
- // URLs — skip if already inside an <a> tag
2418
- safe = safe.replace(/(<a[^>]*>.*?<\\/a>)|(https?:\\/\\/[^\\s<]+)/g, (m, linked, url) => {
2419
- if (linked) return linked;
2420
- return '<a class="commit-link" href="' + url + '" target="_blank">' + url + '</a>';
2421
- });
2422
- return safe;
2423
- }
2944
+ ${COMMIT_MESSAGE_JS}
2424
2945
 
2425
2946
  // --- Find widget ---
2426
2947
  const findBar = document.getElementById('find-bar');
@@ -2531,6 +3052,7 @@ function doSearch(query) {
2531
3052
  if (match) { state.searchMatches.push(idx); row.classList.add('search-match'); }
2532
3053
  });
2533
3054
  document.getElementById('find-count').textContent = state.searchMatches.length + ' match(es)';
3055
+ renderScrollMarkers();
2534
3056
  if (state.searchMatches.length > 0) navigateSearch(0);
2535
3057
  }
2536
3058
 
@@ -2551,6 +3073,7 @@ function clearSearch() {
2551
3073
  state.searchIndex = -1;
2552
3074
  findInput.value = '';
2553
3075
  document.getElementById('find-count').textContent = '';
3076
+ renderScrollMarkers();
2554
3077
  }
2555
3078
 
2556
3079
  function clearSearchHighlights() {
@@ -2563,7 +3086,7 @@ document.addEventListener('keydown', (e) => {
2563
3086
  if (e.key === 'Escape') {
2564
3087
  hideContextMenu();
2565
3088
  if (!findBar.classList.contains('hidden')) { findBar.classList.add('hidden'); clearSearch(); }
2566
- else if (state.expandedCommit) { state.selectedCommit = null; state.expandedCommit = null; document.getElementById('detail-panel').classList.add('hidden'); document.querySelectorAll('.commit-row.selected').forEach(el => el.classList.remove('selected')); }
3089
+ else if (state.expandedCommit) closeDetailPanel();
2567
3090
  }
2568
3091
  });
2569
3092