@hienlh/ppm 0.19.12 → 0.20.1

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 (537) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CLAUDE.md +8 -1
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +4 -1
  5. package/dist/web/assets/accounts-settings-section-_sG1AqtH.js +1 -0
  6. package/dist/web/assets/{ai-resource-editor-CqL-X2zu.js → ai-resource-editor-BLctP1Pr.js} +1 -1
  7. package/dist/web/assets/ai-settings-section-0hSDp7xx.js +1 -0
  8. package/dist/web/assets/{ai-settings-section-CcqokdzF.js → ai-settings-section-CTEU2uGa.js} +1 -1
  9. package/dist/web/assets/{angular-html-C8nsn2VC.js → angular-html-BtJLNcMw.js} +1 -1
  10. package/dist/web/assets/{angular-ts-Caw9nRCH.js → angular-ts-CQiu5F_W.js} +1 -1
  11. package/dist/web/assets/api-client-QfreGirP.js +1 -0
  12. package/dist/web/assets/api-settings-D18Q4Ocp.js +1 -0
  13. package/dist/web/assets/{api-themes-BoOJPqwM.js → api-themes-sSv-6OLk.js} +1 -1
  14. package/dist/web/assets/{apl-Dhu1LORF.js → apl-CKZvgs-w.js} +1 -1
  15. package/dist/web/assets/{appearance-settings-section-2hZBnpPS.js → appearance-settings-section-BkcoSV0E.js} +1 -1
  16. package/dist/web/assets/architecture-PBZL5I3N-DfR8k8Bc.js +1 -0
  17. package/dist/web/assets/{astro-DwkX9sLZ.js → astro-D0b8ssLD.js} +1 -1
  18. package/dist/web/assets/{audio-preview-CkGZO8Uy.js → audio-preview-B4E5_7TW.js} +1 -1
  19. package/dist/web/assets/{blade-D1fC9Osx.js → blade-DOIpINLw.js} +1 -1
  20. package/dist/web/assets/{bsl-pur-PRip.js → bsl-CJ7_JlQ8.js} +1 -1
  21. package/dist/web/assets/{button-2WCAS8dI.js → button-DX32ffge.js} +1 -1
  22. package/dist/web/assets/{cairo-DDJs9amR.js → cairo-5CpV2T1P.js} +1 -1
  23. package/dist/web/assets/chat-tab-CJi4uYb9.js +15 -0
  24. package/dist/web/assets/{cobol-Bo0usdoM.js → cobol-BreMVrtc.js} +1 -1
  25. package/dist/web/assets/code-editor-C4kKhamq.js +7 -0
  26. package/dist/web/assets/{coffee-DBBsbX1e.js → coffee-D7DoKh5b.js} +1 -1
  27. package/dist/web/assets/{conflict-editor-NqFAJP47.js → conflict-editor-BEULKwHk.js} +1 -1
  28. package/dist/web/assets/{context-menu-C13b1uT3.js → context-menu-CJL8L98s.js} +1 -1
  29. package/dist/web/assets/{cpp-6CTO78A-.js → cpp-BnWeWAoE.js} +1 -1
  30. package/dist/web/assets/{crystal-DAlvpAP7.js → crystal-D2pJbFUn.js} +1 -1
  31. package/dist/web/assets/{csv-preview-Dvriy18_.js → csv-preview-k3eM8nFf.js} +1 -1
  32. package/dist/web/assets/{data-grid-overlay-editor-iRrpAV0e.js → data-grid-overlay-editor-CbBkhhJa.js} +1 -1
  33. package/dist/web/assets/{database-viewer-Bs2RMm5a.js → database-viewer-Cslir1dr.js} +1 -1
  34. package/dist/web/assets/diff-viewer-DzAIvBbn.js +4 -0
  35. package/dist/web/assets/{docx-preview-Bgw838ry.js → docx-preview-Xy7SZ7Vf.js} +1 -1
  36. package/dist/web/assets/{edge-D0tZrpC7.js → edge-aqGMtbY3.js} +1 -1
  37. package/dist/web/assets/{edit-diff-preview-DUgmjNre.js → edit-diff-preview-xdDQvPhs.js} +2 -2
  38. package/dist/web/assets/{elixir-C1E6-o4G.js → elixir-CsYMBLc5.js} +1 -1
  39. package/dist/web/assets/{elm-BKyTfeQX.js → elm-BU8C2gFp.js} +1 -1
  40. package/dist/web/assets/{erb-Cdnr4BMI.js → erb-Z_E49ab8.js} +1 -1
  41. package/dist/web/assets/{erlang-N1237mFq.js → erlang-BRqOjXH9.js} +1 -1
  42. package/dist/web/assets/{esm-ClNnmD9t.js → esm-B-vXxHUd.js} +1 -1
  43. package/dist/web/assets/{explorer-body-DfWunSf9.js → explorer-body-D6H-4cz9.js} +2 -2
  44. package/dist/web/assets/explorer-window-content-C5W_JzCz.js +1 -0
  45. package/dist/web/assets/{extension-manager-section-Zc9yag2c.js → extension-manager-section-DwicI3CA.js} +1 -1
  46. package/dist/web/assets/{extension-webview-B5FayDPz.js → extension-webview-Z3HhC8Pd.js} +1 -1
  47. package/dist/web/assets/{files-settings-section-CxAtO3GL.js → files-settings-section-ByV7bRbB.js} +1 -1
  48. package/dist/web/assets/{fortran-fixed-form-D_f9tVv0.js → fortran-fixed-form-CWbxmDGR.js} +1 -1
  49. package/dist/web/assets/{fsharp-DEJmdhc2.js → fsharp-DKMYbq6I.js} +1 -1
  50. package/dist/web/assets/{gdresource-Dmmrx2gP.js → gdresource-B3yFau4V.js} +1 -1
  51. package/dist/web/assets/{general-settings-section-9DfKrBns.js → general-settings-section-nLksKHur.js} +1 -1
  52. package/dist/web/assets/{git-commit-qwJ867ig.js → git-commit-BW0p3Ekk.js} +1 -1
  53. package/dist/web/assets/{git-log-panel-BsmDZurR.js → git-log-panel-iUXltiTB.js} +1 -1
  54. package/dist/web/assets/{git-rebase-Dkv8Zw86.js → git-rebase-DrvmQCS7.js} +1 -1
  55. package/dist/web/assets/gitGraph-HDMCJU4V-CPd78aaT.js +1 -0
  56. package/dist/web/assets/{glide-data-grid-BuYwXIoS.js → glide-data-grid-C1QB4DHt.js} +6 -6
  57. package/dist/web/assets/{glimmer-js-CcrXn8lQ.js → glimmer-js-BY1XApJJ.js} +1 -1
  58. package/dist/web/assets/{glimmer-ts-CLab3lU7.js → glimmer-ts-DPZtxF8N.js} +1 -1
  59. package/dist/web/assets/{glsl-DXhkG9dU.js → glsl-DZ8G_Hvf.js} +1 -1
  60. package/dist/web/assets/{graphql-Cuqsx8r3.js → graphql-Ck06uCZy.js} +1 -1
  61. package/dist/web/assets/group-chat-tab-ezXKVVpg.js +1 -0
  62. package/dist/web/assets/{hack-C_CBVdBc.js → hack-pLogDwAs.js} +1 -1
  63. package/dist/web/assets/{haml-CBWfzZpv.js → haml-CBqeCQ2A.js} +1 -1
  64. package/dist/web/assets/{handlebars-4e8OR-VI.js → handlebars-B66JmVtp.js} +1 -1
  65. package/dist/web/assets/{html-C5X4urn_.js → html-Dk4wmfwC.js} +1 -1
  66. package/dist/web/assets/{html-derivative-nH-jXfsL.js → html-derivative-CJWqfN51.js} +1 -1
  67. package/dist/web/assets/{http-CQgaG7eu.js → http-CexhotTh.js} +1 -1
  68. package/dist/web/assets/{hurl-B8gidJhu.js → hurl-BScKFk3Z.js} +1 -1
  69. package/dist/web/assets/{hxml-BAK94GX8.js → hxml-DAle7vtu.js} +1 -1
  70. package/dist/web/assets/{image-preview-1kGj4W9R.js → image-preview-fuU4h2nr.js} +1 -1
  71. package/dist/web/assets/index-CJWbFurD.js +52 -0
  72. package/dist/web/assets/index-CoaVwAF6.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-BciNds6R.js +1 -0
  74. package/dist/web/assets/{jinja-BuAjvj8M.js → jinja-yHpyD80t.js} +1 -1
  75. package/dist/web/assets/{jira-watcher-section-rJVz40wR.js → jira-watcher-section-DwARwskB.js} +1 -1
  76. package/dist/web/assets/{jison-Dhhs26-L.js → jison-BY6WLJVu.js} +1 -1
  77. package/dist/web/assets/{julia-gZavIyll.js → julia-Dd_iYQDT.js} +1 -1
  78. package/dist/web/assets/{just-DDHrFReO.js → just-BTuUKpxF.js} +1 -1
  79. package/dist/web/assets/keybindings-store-S7YLGd8B.js +1 -0
  80. package/dist/web/assets/{keyboard-shortcuts-section-Dv4e3M8h.js → keyboard-shortcuts-section-BoUDvdDy.js} +1 -1
  81. package/dist/web/assets/{latex-BdGZa8ov.js → latex-Dt3OUlMZ.js} +1 -1
  82. package/dist/web/assets/{liquid-urFfd9Fr.js → liquid-BxPJU2ix.js} +1 -1
  83. package/dist/web/assets/{lua-CcS6tnB5.js → lua-DEGIvn7d.js} +1 -1
  84. package/dist/web/assets/{markdown-renderer-CEloCR06.js → markdown-renderer-DKdfiSIX.js} +3 -3
  85. package/dist/web/assets/markdown-renderer-DYFmMC_8.js +1 -0
  86. package/dist/web/assets/{marko-CSQqk4oG.js → marko-BDxwBJur.js} +1 -1
  87. package/dist/web/assets/{mdc-CYIe80m_.js → mdc-B8aXPTg7.js} +1 -1
  88. package/dist/web/assets/{media-url-DBmHJRft.js → media-url-BMeBaB0q.js} +1 -1
  89. package/dist/web/assets/{mobile-bottom-sheet-B3uDlD79.js → mobile-bottom-sheet-CmySDB6I.js} +1 -1
  90. package/dist/web/assets/mobile-explorer-sheet-CMdqLCV-.js +1 -0
  91. package/dist/web/assets/{nextflow-C-W8vX8I.js → nextflow-DBVZnnT_.js} +1 -1
  92. package/dist/web/assets/{nginx-COGi5you.js → nginx-Crpyd-Do.js} +1 -1
  93. package/dist/web/assets/{nim-CPitYGel.js → nim-BSqTBYDj.js} +1 -1
  94. package/dist/web/assets/notification-store-DpSYFj36.js +1 -0
  95. package/dist/web/assets/{notifications-settings-section-B2GkfB0l.js → notifications-settings-section-CIf3-3xd.js} +1 -1
  96. package/dist/web/assets/{number-overlay-editor-C_eD-Bdr.js → number-overlay-editor-DRjTIcPH.js} +1 -1
  97. package/dist/web/assets/{overview-panel-Cm2HRtEz.js → overview-panel-DiiA3B_Z.js} +1 -1
  98. package/dist/web/assets/packet-RMMSAZCW-ClD3l2Bs.js +1 -0
  99. package/dist/web/assets/{panel-store-BRrnDO_i.js → panel-store-BmdcSBNb.js} +1 -1
  100. package/dist/web/assets/{pdf-preview-Dmz8jkZZ.js → pdf-preview-C-7IdkIK.js} +1 -1
  101. package/dist/web/assets/{perl-B0yW3hbb.js → perl-BU0kbB-K.js} +1 -1
  102. package/dist/web/assets/{php-BNMYN5Jp.js → php-D9n1ikHN.js} +1 -1
  103. package/dist/web/assets/pie-UPGHQEXC-PU6kD0rW.js +1 -0
  104. package/dist/web/assets/{postgres-viewer-BtZbfC3f.js → postgres-viewer-BYXFYlLg.js} +3 -3
  105. package/dist/web/assets/{ppmbot-settings-section-OYfVG5-l.js → ppmbot-settings-section-B9dXvQ6p.js} +1 -1
  106. package/dist/web/assets/{process-table-DRu3NyO_.js → process-table-B6NeTWt8.js} +1 -1
  107. package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-wWZ6bjwq.js} +1 -1
  108. package/dist/web/assets/{proxy-settings-section-xMbrcyr8.js → proxy-settings-section-CUqq7pdU.js} +1 -1
  109. package/dist/web/assets/{pug-CP5fxsxD.js → pug-BOpaczDr.js} +1 -1
  110. package/dist/web/assets/{qml-CHGpQosY.js → qml-B-cQXhrg.js} +1 -1
  111. package/dist/web/assets/{query-audit-section-CC5Mf6qp.js → query-audit-section-C1TYrPIp.js} +1 -1
  112. package/dist/web/assets/radar-KQ55EAFF-4LzjL0Zt.js +1 -0
  113. package/dist/web/assets/{razor-Cfe0AYo4.js → razor-DFB0pNCh.js} +1 -1
  114. package/dist/web/assets/remote-desktop-mobile-sheet-DuKwKe5F.js +2 -0
  115. package/dist/web/assets/{remote-desktop-mobile-view-B4BSHIxU.js → remote-desktop-mobile-view-DxMTtsa6.js} +1 -1
  116. package/dist/web/assets/{remote-desktop-readiness-gate-C0d0PSX4.js → remote-desktop-readiness-gate-BwjfLWqg.js} +1 -1
  117. package/dist/web/assets/{remote-desktop-window-content-DjeCZzqq.js → remote-desktop-window-content-BIl4N5Hn.js} +1 -1
  118. package/dist/web/assets/{rst-DxSSGshb.js → rst-DAXedsgm.js} +1 -1
  119. package/dist/web/assets/{ruby-rbK5SsJa.js → ruby-BCO4polC.js} +1 -1
  120. package/dist/web/assets/{run-in-terminal-DHAvC1AG.js → run-in-terminal-wHKwAnWP.js} +1 -1
  121. package/dist/web/assets/{sas-DDM28fbb.js → sas-DfB5dcFC.js} +1 -1
  122. package/dist/web/assets/schedules-settings-section-C0qOb7GI.js +1 -0
  123. package/dist/web/assets/{scss-TDl-6ro4.js → scss-BknIVgn7.js} +1 -1
  124. package/dist/web/assets/{send-to-chat-BOyUWNKw.js → send-to-chat-BI7-fFpG.js} +1 -1
  125. package/dist/web/assets/{separator-BzGoTlR7.js → separator-W_hJcXPk.js} +1 -1
  126. package/dist/web/assets/{settings-body-CXTQwhbM.js → settings-body-qbrJXX_A.js} +2 -2
  127. package/dist/web/assets/settings-store-CzLnTR8p.js +2 -0
  128. package/dist/web/assets/settings-tab-BKRgYVxf.js +1 -0
  129. package/dist/web/assets/settings-window-content-DWsxJDu-.js +1 -0
  130. package/dist/web/assets/{shaderlab-CCvmDgGS.js → shaderlab-CPu31TpY.js} +1 -1
  131. package/dist/web/assets/{shellsession-BzKS9yKh.js → shellsession-CFX4c_TM.js} +1 -1
  132. package/dist/web/assets/{soy-QfO35nDh.js → soy-rz9hilgm.js} +1 -1
  133. package/dist/web/assets/{sparql-D29RDfp2.js → sparql-Bo8ZDeqE.js} +1 -1
  134. package/dist/web/assets/{sql-query-editor-B-VwfyTf.js → sql-query-editor-CsZOpCW3.js} +1 -1
  135. package/dist/web/assets/{sqlite-viewer-CpUhDEhN.js → sqlite-viewer-DUrnCaP7.js} +1 -1
  136. package/dist/web/assets/{stata-Be6j_SNv.js → stata-B5J-QMyE.js} +1 -1
  137. package/dist/web/assets/{streaming-store-9_9j70zt.js → streaming-store-9XVmAMfV.js} +1 -1
  138. package/dist/web/assets/{surrealql-CykFjgNG.js → surrealql-Caw1ADnG.js} +1 -1
  139. package/dist/web/assets/{svelte-CdBF8z4-.js → svelte-D3oMVcxz.js} +1 -1
  140. package/dist/web/assets/{system-monitor-body-DzWXd1wM.js → system-monitor-body-C5cMpg7I.js} +2 -2
  141. package/dist/web/assets/system-monitor-tab-C66ywbVZ.js +1 -0
  142. package/dist/web/assets/{system-monitor-window-content-DIIP3dff.js → system-monitor-window-content-CENaXYJ-.js} +1 -1
  143. package/dist/web/assets/{tab-host-window-content-CLdcz84K.js → tab-host-window-content-HViABGoS.js} +1 -1
  144. package/dist/web/assets/{tab-store-PZsP5ZwG.js → tab-store-1xYJb0gM.js} +1 -1
  145. package/dist/web/assets/team-member-sheet-CK6eyBRL.js +2 -0
  146. package/dist/web/assets/team-member-window-content-D6wxg37P.js +1 -0
  147. package/dist/web/assets/{templ-BVHE2CNb.js → templ-BAdNyIOL.js} +1 -1
  148. package/dist/web/assets/{terminal-tab-BeKLpaPM.js → terminal-tab-BmyTawmN.js} +2 -2
  149. package/dist/web/assets/{tex-C8XwrbBw.js → tex-IQ8mELOR.js} +1 -1
  150. package/dist/web/assets/tool-cards-y-XJcJHo.js +9 -0
  151. package/dist/web/assets/treemap-KZPCXAKY-DiumLNMg.js +1 -0
  152. package/dist/web/assets/{ts-tags-BIHk5AM0.js → ts-tags-D1DDO2jl.js} +1 -1
  153. package/dist/web/assets/{twig-YcE6Q2Ox.js → twig-C8Fd5RvV.js} +1 -1
  154. package/dist/web/assets/use-accounts-data-BbOb2QNP.js +1 -0
  155. package/dist/web/assets/{use-blob-url-De-ZtcpX.js → use-blob-url-CrwoZJUJ.js} +1 -1
  156. package/dist/web/assets/use-inline-blame-C1G-_NjC.js +8 -0
  157. package/dist/web/assets/{use-monaco-theme-Kj2OGDlm.js → use-monaco-theme-BkM5rMgL.js} +1 -1
  158. package/dist/web/assets/{use-open-team-member-DWhhWdmJ.js → use-open-team-member-CSUU7Z8N.js} +1 -1
  159. package/dist/web/assets/{use-remote-desktop-display-choice-8AkPpM_0.js → use-remote-desktop-display-choice-BT2r1td1.js} +1 -1
  160. package/dist/web/assets/{use-websocket-kR1omKO1.js → use-websocket-BvVTwMjI.js} +1 -1
  161. package/dist/web/assets/{vendor-mermaid-CKKtYuTV.js → vendor-mermaid-CfY1-PkA.js} +1 -1
  162. package/dist/web/assets/{video-preview-BdV7LjbZ.js → video-preview-BEaofjN7.js} +1 -1
  163. package/dist/web/assets/{vue-q7m01V0h.js → vue-EvccXlkP.js} +1 -1
  164. package/dist/web/assets/{vue-html-D1y9feOj.js → vue-html-Dg0xUJCh.js} +1 -1
  165. package/dist/web/assets/{vue-vine-DrIGs03v.js → vue-vine-BdrMg72k.js} +1 -1
  166. package/dist/web/assets/{window-pip-registry-CSLhJfpL.js → window-pip-registry-CrCYYTTf.js} +1 -1
  167. package/dist/web/assets/{window-store-DGKf9rPG.js → window-store-D0HbQ1pM.js} +1 -1
  168. package/dist/web/assets/ws-auth-D111jj32.js +1 -0
  169. package/dist/web/assets/{xml-oyvc1920.js → xml-MmDclzxK.js} +1 -1
  170. package/dist/web/assets/{xsl-BbIGdNPF.js → xsl-DQEFs6Dd.js} +1 -1
  171. package/dist/web/index.html +23 -23
  172. package/dist/web/sw.js +1 -1
  173. package/docs/extension-development-guide.md +47 -0
  174. package/docs/project-roadmap.md +3 -1
  175. package/package.json +2 -2
  176. package/packages/ext-git-graph/package.json +23 -5
  177. package/packages/ext-git-graph/src/blame-parser.test.ts +154 -0
  178. package/packages/ext-git-graph/src/blame-parser.ts +148 -0
  179. package/packages/ext-git-graph/src/blame-view.ts +389 -0
  180. package/packages/ext-git-graph/src/commit-search.test.ts +118 -0
  181. package/packages/ext-git-graph/src/commit-search.ts +102 -0
  182. package/packages/ext-git-graph/src/compare-args.test.ts +111 -0
  183. package/packages/ext-git-graph/src/compare-args.ts +109 -0
  184. package/packages/ext-git-graph/src/compare-view.ts +343 -0
  185. package/packages/ext-git-graph/src/extension-integration.test.ts +6 -3
  186. package/packages/ext-git-graph/src/extension.ts +202 -249
  187. package/packages/ext-git-graph/src/file-history-parser.test.ts +102 -0
  188. package/packages/ext-git-graph/src/file-history-parser.ts +89 -0
  189. package/packages/ext-git-graph/src/file-history-view.ts +386 -0
  190. package/packages/ext-git-graph/src/git-exec.ts +122 -0
  191. package/packages/ext-git-graph/src/git-state.ts +81 -0
  192. package/packages/ext-git-graph/src/panel-nav.ts +65 -0
  193. package/packages/ext-git-graph/src/panel-registry.ts +72 -0
  194. package/packages/ext-git-graph/src/ppm-api.ts +48 -0
  195. package/packages/ext-git-graph/src/rebase-todo.test.ts +196 -0
  196. package/packages/ext-git-graph/src/rebase-todo.ts +119 -0
  197. package/packages/ext-git-graph/src/rebase-view.ts +483 -0
  198. package/packages/ext-git-graph/src/reflog-parser.test.ts +93 -0
  199. package/packages/ext-git-graph/src/reflog-parser.ts +86 -0
  200. package/packages/ext-git-graph/src/reflog-view.ts +375 -0
  201. package/packages/ext-git-graph/src/register-view-command.ts +47 -0
  202. package/packages/ext-git-graph/src/submodule-parser.test.ts +79 -0
  203. package/packages/ext-git-graph/src/submodule-parser.ts +77 -0
  204. package/packages/ext-git-graph/src/types.ts +10 -1
  205. package/packages/ext-git-graph/src/webview-html.ts +438 -3
  206. package/packages/ext-git-graph/src/webview-shell.ts +236 -0
  207. package/src/providers/claude-agent-sdk.ts +7 -0
  208. package/src/providers/codex-app-server/codex-provider.ts +89 -1
  209. package/src/server/index.ts +1 -1
  210. package/src/server/routes/accounts.ts +25 -1
  211. package/src/server/routes/chat.ts +29 -1
  212. package/src/server/routes/codex-accounts.ts +41 -1
  213. package/src/server/routes/git.ts +97 -0
  214. package/src/server/routes/settings.ts +1 -0
  215. package/src/server/routes/teams.ts +14 -3
  216. package/src/services/account-selector.service.ts +125 -20
  217. package/src/services/account.service.ts +176 -54
  218. package/src/services/autostart-generator.ts +12 -1
  219. package/src/services/codex-account.service.ts +87 -13
  220. package/src/services/db.service.ts +58 -3
  221. package/src/services/extension.service.ts +55 -5
  222. package/src/services/git-blame/blame-porcelain.ts +89 -0
  223. package/src/services/git-blame/git-blame.service.ts +90 -0
  224. package/src/services/git-hunks/git-hunks.service.ts +264 -0
  225. package/src/services/git-hunks/unified-diff.ts +359 -0
  226. package/src/services/picked-account-binding.ts +57 -0
  227. package/src/services/team-member-activity/member-transcript-tail.ts +64 -0
  228. package/src/shared/blame.ts +79 -0
  229. package/src/web/components/chat/chat-history-bar.tsx +34 -5
  230. package/src/web/components/chat/chat-tab.tsx +120 -3
  231. package/src/web/components/chat/codex-usage-panel.tsx +132 -14
  232. package/src/web/components/chat/edit-diff-preview.tsx +14 -7
  233. package/src/web/components/chat/team-member-window-content.tsx +8 -39
  234. package/src/web/components/chat/usage-badge.tsx +176 -52
  235. package/src/web/components/editor/code-editor.tsx +33 -1
  236. package/src/web/components/editor/diff-viewer.tsx +83 -8
  237. package/src/web/components/editor/editor-toolbar.tsx +25 -1
  238. package/src/web/components/git/git-status-panel.tsx +46 -0
  239. package/src/web/components/git/hunk-selection.ts +132 -0
  240. package/src/web/components/git/hunk-stage-dialog.tsx +419 -0
  241. package/src/web/components/settings/accounts/account-bucket-row.tsx +19 -3
  242. package/src/web/components/settings/accounts/account-card.tsx +153 -33
  243. package/src/web/components/settings/accounts/account-hint.tsx +33 -0
  244. package/src/web/components/settings/accounts/account-usage-format.ts +26 -0
  245. package/src/web/components/settings/accounts/codex-accounts-section.tsx +24 -9
  246. package/src/web/components/settings/accounts/codex-usage-rows.tsx +2 -2
  247. package/src/web/components/settings/accounts/use-accounts-data.ts +32 -2
  248. package/src/web/components/settings/accounts/use-codex-accounts.ts +24 -2
  249. package/src/web/components/shared/markdown-code-block.tsx +10 -5
  250. package/src/web/hooks/use-chat.ts +27 -0
  251. package/src/web/hooks/use-inline-blame.ts +175 -0
  252. package/src/web/hooks/use-member-transcript-tail.ts +95 -0
  253. package/src/web/lib/api-settings.ts +24 -0
  254. package/src/web/stores/settings-store.ts +12 -0
  255. package/dist/web/assets/accounts-settings-section-Cd1Nh04o.js +0 -1
  256. package/dist/web/assets/ai-settings-section-OaYGT5OF.js +0 -1
  257. package/dist/web/assets/api-client-BogqsFna.js +0 -1
  258. package/dist/web/assets/api-settings-BVfemzZW.js +0 -1
  259. package/dist/web/assets/architecture-PBZL5I3N-BZBxtErN.js +0 -1
  260. package/dist/web/assets/chat-tab-Cc1W2pTu.js +0 -15
  261. package/dist/web/assets/code-editor-gLRyCPWE.js +0 -7
  262. package/dist/web/assets/diff-viewer-ix1Ko0xk.js +0 -4
  263. package/dist/web/assets/explorer-window-content-B7HmteDI.js +0 -1
  264. package/dist/web/assets/gitGraph-HDMCJU4V-BHxJbXfV.js +0 -1
  265. package/dist/web/assets/group-chat-tab-DmZ1vLk-.js +0 -1
  266. package/dist/web/assets/index-N-pnuwFP.css +0 -2
  267. package/dist/web/assets/index-yV5RgBDh.js +0 -52
  268. package/dist/web/assets/info-3K5VOQVL-DI4p3vbH.js +0 -1
  269. package/dist/web/assets/keybindings-store-88_Zkn5F.js +0 -1
  270. package/dist/web/assets/markdown-renderer-UhszMWSn.js +0 -1
  271. package/dist/web/assets/mobile-explorer-sheet-D9cqcU3X.js +0 -1
  272. package/dist/web/assets/notification-store-B6X6RYqQ.js +0 -1
  273. package/dist/web/assets/packet-RMMSAZCW-YNQWTi_-.js +0 -1
  274. package/dist/web/assets/pie-UPGHQEXC-E3Wiw76n.js +0 -1
  275. package/dist/web/assets/radar-KQ55EAFF-BPQJUKON.js +0 -1
  276. package/dist/web/assets/remote-desktop-mobile-sheet-DefltAPl.js +0 -2
  277. package/dist/web/assets/schedules-settings-section-D76Ob6tI.js +0 -1
  278. package/dist/web/assets/settings-store-DWUx3865.js +0 -2
  279. package/dist/web/assets/settings-tab-DToHQXfC.js +0 -1
  280. package/dist/web/assets/settings-window-content-B2rlq-Dv.js +0 -1
  281. package/dist/web/assets/system-monitor-tab-CuDpwAZU.js +0 -1
  282. package/dist/web/assets/team-member-sheet-DLrT_Kxo.js +0 -2
  283. package/dist/web/assets/team-member-window-content-S8cdLcg3.js +0 -1
  284. package/dist/web/assets/tool-cards-CLxrQlUs.js +0 -9
  285. package/dist/web/assets/treemap-KZPCXAKY-C2CvKBQR.js +0 -1
  286. package/dist/web/assets/use-accounts-data-snYPJED_.js +0 -1
  287. package/dist/web/assets/ws-auth-ZSZkAT6p.js +0 -1
  288. /package/dist/web/assets/{abap-Cq9nYQyd.js → abap-D-o-bvtI.js} +0 -0
  289. /package/dist/web/assets/{actionscript-3-DL8rUIy0.js → actionscript-3-D2tIzAm8.js} +0 -0
  290. /package/dist/web/assets/{ada-BY8TLuhY.js → ada-DxatBcxS.js} +0 -0
  291. /package/dist/web/assets/{andromeeda-CyVfHsS0.js → andromeeda-D0LK9YJ3.js} +0 -0
  292. /package/dist/web/assets/{apache-DTQUy73x.js → apache-B7uMPNiW.js} +0 -0
  293. /package/dist/web/assets/{apex-CiSZngrZ.js → apex-B7GiXrAr.js} +0 -0
  294. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-CEYCBt_0.js} +0 -0
  295. /package/dist/web/assets/{applescript-CzuRDd3h.js → applescript-D_SIFkKe.js} +0 -0
  296. /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-BVXAUYoN.js} +0 -0
  297. /package/dist/web/assets/{ara-CSFV93kR.js → ara-C3iECgdg.js} +0 -0
  298. /package/dist/web/assets/{asciidoc-hOQNnXhz.js → asciidoc-gc8z_MMY.js} +0 -0
  299. /package/dist/web/assets/{asm-DdG53NGR.js → asm-27qeoPOI.js} +0 -0
  300. /package/dist/web/assets/{aurora-x-DJ7ORZG6.js → aurora-x-C5J9fWe1.js} +0 -0
  301. /package/dist/web/assets/{awk-B3FFpX_J.js → awk-B7GBzF92.js} +0 -0
  302. /package/dist/web/assets/{ayu-dark-D_P1CfTe.js → ayu-dark-XlnQhuLL.js} +0 -0
  303. /package/dist/web/assets/{ayu-light-Cz3YLUqH.js → ayu-light-DKLLilkU.js} +0 -0
  304. /package/dist/web/assets/{ayu-mirage-JcMq5tgJ.js → ayu-mirage-Dv-XOprJ.js} +0 -0
  305. /package/dist/web/assets/{ballerina-7Hs4Rfpk.js → ballerina-DACRT-sT.js} +0 -0
  306. /package/dist/web/assets/{bat-COgQdnFA.js → bat-BlQCq2XE.js} +0 -0
  307. /package/dist/web/assets/{beancount-BJ3CutBG.js → beancount-DYDztRI0.js} +0 -0
  308. /package/dist/web/assets/{berry-D7so2Cui.js → berry-BE5YKqvx.js} +0 -0
  309. /package/dist/web/assets/{bibtex-Ct3acHa1.js → bibtex-CAm7mgEm.js} +0 -0
  310. /package/dist/web/assets/{bicep-jXDwCQcm.js → bicep-BKgAbfVa.js} +0 -0
  311. /package/dist/web/assets/{bird2-CjLD2cna.js → bird2-yrBNbW1G.js} +0 -0
  312. /package/dist/web/assets/{c-CdgZojnL.js → c-txZFr4RM.js} +0 -0
  313. /package/dist/web/assets/{c3-DtpZYego.js → c3-D_DrzBUd.js} +0 -0
  314. /package/dist/web/assets/{cadence-CPLkixoh.js → cadence-CUFCEgG9.js} +0 -0
  315. /package/dist/web/assets/{can-open-in-ppm-BntPywsy.js → can-open-in-ppm-_yqa69dV.js} +0 -0
  316. /package/dist/web/assets/{catppuccin-frappe-Xt0_qJa9.js → catppuccin-frappe-Dn32MuhA.js} +0 -0
  317. /package/dist/web/assets/{catppuccin-latte-CJnbLec5.js → catppuccin-latte-Dewo7yDf.js} +0 -0
  318. /package/dist/web/assets/{catppuccin-macchiato-BPHP1wYB.js → catppuccin-macchiato-D8WOWZeo.js} +0 -0
  319. /package/dist/web/assets/{catppuccin-mocha-ssWJodGc.js → catppuccin-mocha-b0md48XM.js} +0 -0
  320. /package/dist/web/assets/{clarity-DB0408F6.js → clarity-Ct3UqLBj.js} +0 -0
  321. /package/dist/web/assets/{clojure-dpRPlAO4.js → clojure-CwP5aPEo.js} +0 -0
  322. /package/dist/web/assets/{cmake-r2VkeX9y.js → cmake-D5RF49e5.js} +0 -0
  323. /package/dist/web/assets/{codeowners-v5nQqHiB.js → codeowners-Cl5897L_.js} +0 -0
  324. /package/dist/web/assets/{codeql-BSSEhKRR.js → codeql-BKh1RCg0.js} +0 -0
  325. /package/dist/web/assets/{common-lisp-BXb2MMMA.js → common-lisp-BhHuD6t0.js} +0 -0
  326. /package/dist/web/assets/{coq-BjBNi73X.js → coq-Dco3qwD8.js} +0 -0
  327. /package/dist/web/assets/{csharp-CdPjZRbV.js → csharp-Dpjw9ZA9.js} +0 -0
  328. /package/dist/web/assets/{css-BuxbQOHL.js → css-CPe4cabH.js} +0 -0
  329. /package/dist/web/assets/{csv-Dj-eoZwH.js → csv-BAcTN8Kt.js} +0 -0
  330. /package/dist/web/assets/{csv-parser-CBbALsFU.js → csv-parser-DbxGKdMG.js} +0 -0
  331. /package/dist/web/assets/{cue-Dz73VksN.js → cue-Cr6y60TA.js} +0 -0
  332. /package/dist/web/assets/{cypher-CkVkDywZ.js → cypher-DWtqXXFP.js} +0 -0
  333. /package/dist/web/assets/{d-oU51Pbpf.js → d-BASiTZk4.js} +0 -0
  334. /package/dist/web/assets/{dark-plus-DcwvjjfO.js → dark-plus-56kmnrcl.js} +0 -0
  335. /package/dist/web/assets/{dart-DGvu8mcX.js → dart-C5mZnjoE.js} +0 -0
  336. /package/dist/web/assets/{data-grid-types-B6kUrF-M.js → data-grid-types-DeY60CLU.js} +0 -0
  337. /package/dist/web/assets/{dax-BmbjKQ90.js → dax-B4I2e7d8.js} +0 -0
  338. /package/dist/web/assets/{desktop-IRBSiFfh.js → desktop-DekGg0BT.js} +0 -0
  339. /package/dist/web/assets/{diff-Bdik2JR8.js → diff-CWnJBd73.js} +0 -0
  340. /package/dist/web/assets/{dist-Bv4zQpS1.js → dist-B0J2eYBz.js} +0 -0
  341. /package/dist/web/assets/{dist-C8YPBIOb.js → dist-BKOM_AoT.js} +0 -0
  342. /package/dist/web/assets/{docker-Bgdl0H8I.js → docker-DevchDxK.js} +0 -0
  343. /package/dist/web/assets/{dotenv-COe5NdnA.js → dotenv-DCvtdaGJ.js} +0 -0
  344. /package/dist/web/assets/{dracula-DjURaSAa.js → dracula-8U6Vkk3l.js} +0 -0
  345. /package/dist/web/assets/{dracula-soft-Do7d0bo-.js → dracula-soft-Bfeew5Wg.js} +0 -0
  346. /package/dist/web/assets/{dream-maker-DYwQvJIj.js → dream-maker-DDTJVuoy.js} +0 -0
  347. /package/dist/web/assets/{emacs-lisp-BXjlHyDW.js → emacs-lisp-CEHJqI4m.js} +0 -0
  348. /package/dist/web/assets/{everforest-dark-h8Q2loiX.js → everforest-dark-CHHLt7cq.js} +0 -0
  349. /package/dist/web/assets/{everforest-light-CdsJiL-9.js → everforest-light-TcTbK1sM.js} +0 -0
  350. /package/dist/web/assets/{fennel-D0CBtOvY.js → fennel-Bh4vsA1A.js} +0 -0
  351. /package/dist/web/assets/{fish-DzayfrMj.js → fish-D0T0fILG.js} +0 -0
  352. /package/dist/web/assets/{fluent-CatjkUfx.js → fluent-CgXDXdyy.js} +0 -0
  353. /package/dist/web/assets/{format-bytes-BulRSULX.js → format-bytes-Bc6SqF7M.js} +0 -0
  354. /package/dist/web/assets/{fortran-free-form-DM04ud0b.js → fortran-free-form-CUg3Xhz4.js} +0 -0
  355. /package/dist/web/assets/{gdscript-BLN5iZwf.js → gdscript-Cx056p_k.js} +0 -0
  356. /package/dist/web/assets/{gdshader-BK-1iZQs.js → gdshader-Dj1qP4I5.js} +0 -0
  357. /package/dist/web/assets/{genie-BGPOFWDN.js → genie-SBt15wn7.js} +0 -0
  358. /package/dist/web/assets/{gherkin-CdVOJ_JY.js → gherkin-bTJiHIrB.js} +0 -0
  359. /package/dist/web/assets/{github-dark-B_xW8TaY.js → github-dark-BOPhyugs.js} +0 -0
  360. /package/dist/web/assets/{github-dark-default-h_iKq7CX.js → github-dark-default-Cktqe6NF.js} +0 -0
  361. /package/dist/web/assets/{github-dark-dimmed-nKDYE62T.js → github-dark-dimmed-CeWfII_B.js} +0 -0
  362. /package/dist/web/assets/{github-dark-high-contrast-l8I771QT.js → github-dark-high-contrast-CTx8LpdV.js} +0 -0
  363. /package/dist/web/assets/{github-light-DNhjkg8Z.js → github-light-DVsO5Un1.js} +0 -0
  364. /package/dist/web/assets/{github-light-default-DQ06uS3g.js → github-light-default-BiyPxicr.js} +0 -0
  365. /package/dist/web/assets/{github-light-high-contrast-qc2sZCVN.js → github-light-high-contrast-BPlnke0a.js} +0 -0
  366. /package/dist/web/assets/{gleam-DPPatkNH.js → gleam-CWSxvD0C.js} +0 -0
  367. /package/dist/web/assets/{gn-BnzGQTy5.js → gn-D7NUVJuv.js} +0 -0
  368. /package/dist/web/assets/{gnuplot-Ct1leoo3.js → gnuplot-c5uXmJKc.js} +0 -0
  369. /package/dist/web/assets/{go-qysCdbrn.js → go-CRsHi4Cw.js} +0 -0
  370. /package/dist/web/assets/{groovy-BkWIdrRQ.js → groovy-DpV0RA5r.js} +0 -0
  371. /package/dist/web/assets/{gruvbox-dark-hard-L6Iiybbl.js → gruvbox-dark-hard-DD9fguUN.js} +0 -0
  372. /package/dist/web/assets/{gruvbox-dark-medium-DbD3yl0b.js → gruvbox-dark-medium-BvcwpYJ-.js} +0 -0
  373. /package/dist/web/assets/{gruvbox-dark-soft-Ban2cHK3.js → gruvbox-dark-soft-48dCxX9M.js} +0 -0
  374. /package/dist/web/assets/{gruvbox-light-hard-CK_SjS3e.js → gruvbox-light-hard-CjJ994Ip.js} +0 -0
  375. /package/dist/web/assets/{gruvbox-light-medium-Bjgvhc2w.js → gruvbox-light-medium-DQJQZ_rV.js} +0 -0
  376. /package/dist/web/assets/{gruvbox-light-soft-EWnyYGUu.js → gruvbox-light-soft-AqsC9mKw.js} +0 -0
  377. /package/dist/web/assets/{haskell-rT1N9nYr.js → haskell-B5V7yvmh.js} +0 -0
  378. /package/dist/web/assets/{haxe-Bbc91axo.js → haxe-LWTKfPKr.js} +0 -0
  379. /package/dist/web/assets/{hcl-D-3qJA4v.js → hcl-QmNZdAtI.js} +0 -0
  380. /package/dist/web/assets/{hjson-DWtewcyQ.js → hjson-CboJsAzK.js} +0 -0
  381. /package/dist/web/assets/{hlsl-C7t6hCrO.js → hlsl-Bd-DO_WL.js} +0 -0
  382. /package/dist/web/assets/{horizon-zJAGUQ3j.js → horizon-CU8Bi_ss.js} +0 -0
  383. /package/dist/web/assets/{horizon-bright-CRNyrJy8.js → horizon-bright-iSQ3_3jP.js} +0 -0
  384. /package/dist/web/assets/{houston-BFKa4W1-.js → houston-CTXv3Puz.js} +0 -0
  385. /package/dist/web/assets/{hy-W1hNjMBM.js → hy-Cd4hTidD.js} +0 -0
  386. /package/dist/web/assets/{image-gallery-BycaVWOu.js → image-gallery-J8h8IY0c.js} +0 -0
  387. /package/dist/web/assets/{imba-CPphaV-V.js → imba-BipCHEY2.js} +0 -0
  388. /package/dist/web/assets/{ini-CayaZ4vW.js → ini-Dd1KkMrB.js} +0 -0
  389. /package/dist/web/assets/{java-BPAE_9Ej.js → java-rGr0OQD8.js} +0 -0
  390. /package/dist/web/assets/{javascript-C5SKIVqe.js → javascript-DHhCH2TN.js} +0 -0
  391. /package/dist/web/assets/{json-C-0EUYmu.js → json-CMMLBCUf.js} +0 -0
  392. /package/dist/web/assets/{json5-BqVetEZL.js → json5-mPOwTOsB.js} +0 -0
  393. /package/dist/web/assets/{jsonc-DnjIzkUB.js → jsonc-Bie0E6D3.js} +0 -0
  394. /package/dist/web/assets/{jsonl-rbV-3EqO.js → jsonl-CpqHmtC1.js} +0 -0
  395. /package/dist/web/assets/{jsonnet-9hvXw4qr.js → jsonnet-D21Ms51e.js} +0 -0
  396. /package/dist/web/assets/{jssm-1ifXyIhu.js → jssm-BXnl1nur.js} +0 -0
  397. /package/dist/web/assets/{jsx-hPumywTY.js → jsx-Chnii_vB.js} +0 -0
  398. /package/dist/web/assets/{kanagawa-dragon-BCPjmoMO.js → kanagawa-dragon-DhxR0UjL.js} +0 -0
  399. /package/dist/web/assets/{kanagawa-lotus-DN2VNjy7.js → kanagawa-lotus-fupRg5wp.js} +0 -0
  400. /package/dist/web/assets/{kanagawa-wave-B8RZEsgc.js → kanagawa-wave-DSWbRS9M.js} +0 -0
  401. /package/dist/web/assets/{kdl-CkvjWQIa.js → kdl-DirGHqUy.js} +0 -0
  402. /package/dist/web/assets/{kotlin-D8cnRi5o.js → kotlin-DkhUVMDC.js} +0 -0
  403. /package/dist/web/assets/{kusto-CmeeBEJG.js → kusto-DU7OpovF.js} +0 -0
  404. /package/dist/web/assets/{laserwave--rtV8Mv9.js → laserwave-iLKb1ZCT.js} +0 -0
  405. /package/dist/web/assets/{lean-CS-Pyi42.js → lean-Cuvh_QAP.js} +0 -0
  406. /package/dist/web/assets/{less-Ddi_7qGm.js → less-B5tQZOdI.js} +0 -0
  407. /package/dist/web/assets/{lib-DIrxSyLb.js → lib-el9rA30D.js} +0 -0
  408. /package/dist/web/assets/{libesm-CYLrAeW3.js → libesm-CdttTIAd.js} +0 -0
  409. /package/dist/web/assets/{light-plus-CrEjpkkX.js → light-plus-DiHU2KL5.js} +0 -0
  410. /package/dist/web/assets/{llvm-Cm7ZXmAO.js → llvm-sCBTKCKN.js} +0 -0
  411. /package/dist/web/assets/{log-lW2sYxtp.js → log-DtyR34Ie.js} +0 -0
  412. /package/dist/web/assets/{logo-B-PiRb53.js → logo-CP91aQyG.js} +0 -0
  413. /package/dist/web/assets/{luau-8St0pC-R.js → luau-nfGo2ulP.js} +0 -0
  414. /package/dist/web/assets/{make-DbNdhH54.js → make-XbDn9GHe.js} +0 -0
  415. /package/dist/web/assets/{markdown-CYocYSGd.js → markdown-BB1_3xLr.js} +0 -0
  416. /package/dist/web/assets/{material-theme-CVBF1Cbi.js → material-theme-CIx_sxAy.js} +0 -0
  417. /package/dist/web/assets/{material-theme-darker-HFrlNZD8.js → material-theme-darker-DsQeueDD.js} +0 -0
  418. /package/dist/web/assets/{material-theme-lighter-BM-uz2ft.js → material-theme-lighter-x_lMi74j.js} +0 -0
  419. /package/dist/web/assets/{material-theme-ocean-PbvUez8m.js → material-theme-ocean-Bkgh25P8.js} +0 -0
  420. /package/dist/web/assets/{material-theme-palenight-B22Ck6qd.js → material-theme-palenight-CoOjdbti.js} +0 -0
  421. /package/dist/web/assets/{matlab-GFFNzucw.js → matlab-D6xZas_8.js} +0 -0
  422. /package/dist/web/assets/{mdx-BVnJzMDx.js → mdx-Cl5_YNoz.js} +0 -0
  423. /package/dist/web/assets/{mermaid-BlCmSTCM.js → mermaid-8Eow4jz_.js} +0 -0
  424. /package/dist/web/assets/{min-dark-B0PwlAfC.js → min-dark-D4DhjgRE.js} +0 -0
  425. /package/dist/web/assets/{min-light-C_H2ABKu.js → min-light-DzAckd0t.js} +0 -0
  426. /package/dist/web/assets/{mipsasm-C3rf2fo6.js → mipsasm-zI_zu0tj.js} +0 -0
  427. /package/dist/web/assets/{mojo-CDRuzM3o.js → mojo-DQnGGPHf.js} +0 -0
  428. /package/dist/web/assets/{monokai-KiebNy_Y.js → monokai-CrFoD2VD.js} +0 -0
  429. /package/dist/web/assets/{moonbit-CdVUHoWl.js → moonbit-DdphIGs6.js} +0 -0
  430. /package/dist/web/assets/{move-Dq9iKZRY.js → move-BUlkn0xW.js} +0 -0
  431. /package/dist/web/assets/{narrat-7y5L5sGM.js → narrat-CbOBdtCn.js} +0 -0
  432. /package/dist/web/assets/{nextflow-groovy-Cwcbkcrx.js → nextflow-groovy-CeVLUTuQ.js} +0 -0
  433. /package/dist/web/assets/{night-owl-TBgLPzj4.js → night-owl-Bm4LhUR1.js} +0 -0
  434. /package/dist/web/assets/{night-owl-light-Dehb9My2.js → night-owl-light-DtcBiMEf.js} +0 -0
  435. /package/dist/web/assets/{nix-BKj-gzoX.js → nix-DrdwQ70g.js} +0 -0
  436. /package/dist/web/assets/{nord-BF_r8PeX.js → nord-B0Head1v.js} +0 -0
  437. /package/dist/web/assets/{nushell-CeAjk9_E.js → nushell-CmUdltKn.js} +0 -0
  438. /package/dist/web/assets/{objective-c-Xl1OMcDp.js → objective-c-BGDR33XN.js} +0 -0
  439. /package/dist/web/assets/{objective-cpp-C4ZgzrRq.js → objective-cpp-Buc1Zhd0.js} +0 -0
  440. /package/dist/web/assets/{ocaml-CGj8IYTq.js → ocaml-xdzl9rRD.js} +0 -0
  441. /package/dist/web/assets/{odin-C4veqvW2.js → odin-DiUFngnd.js} +0 -0
  442. /package/dist/web/assets/{one-dark-pro-gAcWejsV.js → one-dark-pro-BURxmIp3.js} +0 -0
  443. /package/dist/web/assets/{one-light-B7E-UUXy.js → one-light-BMRMInAj.js} +0 -0
  444. /package/dist/web/assets/{openscad-C-wN9BEv.js → openscad-DAgLA5J3.js} +0 -0
  445. /package/dist/web/assets/{pascal-C8WBtxMs.js → pascal-CoDZxcRB.js} +0 -0
  446. /package/dist/web/assets/{pkl-BBfF4G2A.js → pkl-DzKM6BuM.js} +0 -0
  447. /package/dist/web/assets/{plastic-DFOAb89P.js → plastic-vF15LmNS.js} +0 -0
  448. /package/dist/web/assets/{plsql-BS2CEMMV.js → plsql-C1v07teA.js} +0 -0
  449. /package/dist/web/assets/{po-BKJDtPmy.js → po-CLLC8BuA.js} +0 -0
  450. /package/dist/web/assets/{poimandres-Cg3hL13e.js → poimandres-Uq0xysxM.js} +0 -0
  451. /package/dist/web/assets/{polar-B9ebiXUU.js → polar-2-luY1Pc.js} +0 -0
  452. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DKymYOY-.js} +0 -0
  453. /package/dist/web/assets/{postcss-B5REwNY_.js → postcss-lbFhe0rz.js} +0 -0
  454. /package/dist/web/assets/{powerquery-stc0Gzkf.js → powerquery-RL8O9Wie.js} +0 -0
  455. /package/dist/web/assets/{powershell-jgd0ahJN.js → powershell-upFWhb-b.js} +0 -0
  456. /package/dist/web/assets/{prisma-CWuZjRhI.js → prisma-DBtYaWFk.js} +0 -0
  457. /package/dist/web/assets/{prolog-D5zNFE63.js → prolog-D8jSMykJ.js} +0 -0
  458. /package/dist/web/assets/{proto-B-6RrU7n.js → proto-BM4eadF7.js} +0 -0
  459. /package/dist/web/assets/{puppet-DONP8TDR.js → puppet-BQDQ3Snu.js} +0 -0
  460. /package/dist/web/assets/{purescript-DDJITf-A.js → purescript-Cj2aTX4s.js} +0 -0
  461. /package/dist/web/assets/{python-bmWy9j1-.js → python-BYpqq6u4.js} +0 -0
  462. /package/dist/web/assets/{qmldir-CKDwMt7X.js → qmldir-4uiTcwAu.js} +0 -0
  463. /package/dist/web/assets/{qss-DptQQzBn.js → qss-D5xB-Mqo.js} +0 -0
  464. /package/dist/web/assets/{r-BorKpJ8r.js → r-P6Ujy8rf.js} +0 -0
  465. /package/dist/web/assets/{racket-BaHpYVsZ.js → racket-COKgCFuM.js} +0 -0
  466. /package/dist/web/assets/{raku-DFJIV4k_.js → raku-z14Q9i7K.js} +0 -0
  467. /package/dist/web/assets/{react-X4jUntEf.js → react-D1Yg256j.js} +0 -0
  468. /package/dist/web/assets/{red-70oJGrkb.js → red-ObAvgvQ5.js} +0 -0
  469. /package/dist/web/assets/{reg-C4g235Dv.js → reg-Qv4f0JFA.js} +0 -0
  470. /package/dist/web/assets/{regexp-Cw1UVmti.js → regexp-CEOVGIzy.js} +0 -0
  471. /package/dist/web/assets/{rel-DeLhIz5L.js → rel-cWOyTUwd.js} +0 -0
  472. /package/dist/web/assets/{resolve-theme-b_yGlUFQ.js → resolve-theme-CMp5xxCW.js} +0 -0
  473. /package/dist/web/assets/{riscv-ChnTiBRK.js → riscv-BZDwx-lv.js} +0 -0
  474. /package/dist/web/assets/{ron-C5IWeqDn.js → ron-BpjosPS0.js} +0 -0
  475. /package/dist/web/assets/{rose-pine-DCR0ohYW.js → rose-pine-DhxfJWcm.js} +0 -0
  476. /package/dist/web/assets/{rose-pine-dawn-DvGZs1PR.js → rose-pine-dawn-BbGt9Wy_.js} +0 -0
  477. /package/dist/web/assets/{rose-pine-moon-BWACHjeS.js → rose-pine-moon-dj4Zxgik.js} +0 -0
  478. /package/dist/web/assets/{rosmsg-CQlIVazE.js → rosmsg-EciOaHo2.js} +0 -0
  479. /package/dist/web/assets/{rust-DGGVzWI8.js → rust-C_eH59O_.js} +0 -0
  480. /package/dist/web/assets/{sass-bj1C0rFI.js → sass-Dos3R5lL.js} +0 -0
  481. /package/dist/web/assets/{scala-MU1tLKDi.js → scala-8lVKsTvf.js} +0 -0
  482. /package/dist/web/assets/{scheme-CeGXtv7t.js → scheme-Bd3VdaZj.js} +0 -0
  483. /package/dist/web/assets/{sdbl-D0HLSGKp.js → sdbl-C4Yrjlgh.js} +0 -0
  484. /package/dist/web/assets/{shellscript-DkYiDvh1.js → shellscript-OFZbPKZH.js} +0 -0
  485. /package/dist/web/assets/{slack-dark-YqeHiGWH.js → slack-dark-aL6vlxZs.js} +0 -0
  486. /package/dist/web/assets/{slack-ochin-9Oe0DljJ.js → slack-ochin-BKt1E7sL.js} +0 -0
  487. /package/dist/web/assets/{smalltalk-CaaxedR7.js → smalltalk-D6qVBL1U.js} +0 -0
  488. /package/dist/web/assets/{snazzy-light-C8s2FHNC.js → snazzy-light-BrWerT0t.js} +0 -0
  489. /package/dist/web/assets/{solarized-dark-0ryY6oN3.js → solarized-dark-CzxTJgVQ.js} +0 -0
  490. /package/dist/web/assets/{solarized-light-bltgZeNh.js → solarized-light-BecaDAnG.js} +0 -0
  491. /package/dist/web/assets/{solidity-DOf5HmLZ.js → solidity-C9t7pZ84.js} +0 -0
  492. /package/dist/web/assets/{splunk-D1u4WHDW.js → splunk-CtxfFKtF.js} +0 -0
  493. /package/dist/web/assets/{sql-G8uQZljX.js → sql-7FE_Duz0.js} +0 -0
  494. /package/dist/web/assets/{ssh-config-rbXvly7Q.js → ssh-config-3Fr37ACC.js} +0 -0
  495. /package/dist/web/assets/{stylus-Cii1iADy.js → stylus-BP4mE1sO.js} +0 -0
  496. /package/dist/web/assets/{swift-DpIgonN4.js → swift-0uqTFouD.js} +0 -0
  497. /package/dist/web/assets/{synthwave-84-DjGLzU2b.js → synthwave-84-DQaNM3DI.js} +0 -0
  498. /package/dist/web/assets/{system-verilog-_Z0M0Hs1.js → system-verilog-DUIVrZHZ.js} +0 -0
  499. /package/dist/web/assets/{systemd-BhyDCh9P.js → systemd-CtNo_WIh.js} +0 -0
  500. /package/dist/web/assets/{talonscript-CNchgCRD.js → talonscript-B0HXaCVT.js} +0 -0
  501. /package/dist/web/assets/{tasl-m--W7Pyr.js → tasl-Cb7RAl11.js} +0 -0
  502. /package/dist/web/assets/{tcl-gY-M_2BC.js → tcl-B79aC6l5.js} +0 -0
  503. /package/dist/web/assets/{terraform-CB6CzMwo.js → terraform-75jJDjpW.js} +0 -0
  504. /package/dist/web/assets/{tokyo-night-COh9oRYi.js → tokyo-night-Cz9Uh9qb.js} +0 -0
  505. /package/dist/web/assets/{toml-Bg2bsY-v.js → toml-CD6y9A-z.js} +0 -0
  506. /package/dist/web/assets/{tsv-DceGDiJC.js → tsv-CXJMbBrb.js} +0 -0
  507. /package/dist/web/assets/{tsx-EccDYa7i.js → tsx-BuYN0I2t.js} +0 -0
  508. /package/dist/web/assets/{turtle-C_l2JhvX.js → turtle-DWH-Rjse.js} +0 -0
  509. /package/dist/web/assets/{typescript-C1ytmMcr.js → typescript-BPd-hsrR.js} +0 -0
  510. /package/dist/web/assets/{typespec-De2ztzG5.js → typespec-B0wvVykx.js} +0 -0
  511. /package/dist/web/assets/{typst-DNcS7gSr.js → typst-DWG2c48-.js} +0 -0
  512. /package/dist/web/assets/{use-is-mobile-CYW3FJFE.js → use-is-mobile-D1weanbj.js} +0 -0
  513. /package/dist/web/assets/{use-visual-viewport-D9O48HQt.js → use-visual-viewport-Bfl3Bxxb.js} +0 -0
  514. /package/dist/web/assets/{utils-C9OjCrou.js → utils-IrnQujwR.js} +0 -0
  515. /package/dist/web/assets/{v-DSMUsdMO.js → v-Ch6E4Egd.js} +0 -0
  516. /package/dist/web/assets/{vala-BEazLpkO.js → vala-CGVKwbPx.js} +0 -0
  517. /package/dist/web/assets/{vb-CV9pEJzW.js → vb-BIEbSxyT.js} +0 -0
  518. /package/dist/web/assets/{vendor-xterm-cuGQmUjN.js → vendor-xterm-YQlzIWv2.js} +0 -0
  519. /package/dist/web/assets/{verilog-DhXkLVMk.js → verilog-A-3pYPad.js} +0 -0
  520. /package/dist/web/assets/{vesper-n4IltYq-.js → vesper-CK1naZAV.js} +0 -0
  521. /package/dist/web/assets/{vhdl-CePjFbvs.js → vhdl-CVawmgMg.js} +0 -0
  522. /package/dist/web/assets/{viml-ChndFsm5.js → viml-CiWjaqBY.js} +0 -0
  523. /package/dist/web/assets/{vitesse-black-BytVdvhC.js → vitesse-black-PYXW4-BO.js} +0 -0
  524. /package/dist/web/assets/{vitesse-dark-83Wech0N.js → vitesse-dark-BU5s4AuG.js} +0 -0
  525. /package/dist/web/assets/{vitesse-light-Xg4wbeMO.js → vitesse-light-DgkIT-N7.js} +0 -0
  526. /package/dist/web/assets/{vyper-eW1NC3FG.js → vyper-D8cRDPp3.js} +0 -0
  527. /package/dist/web/assets/{wasm-BucxKwBZ.js → wasm-C02wlRbP.js} +0 -0
  528. /package/dist/web/assets/{wasm-Bhucqu1z.js → wasm-ClFJgzmT.js} +0 -0
  529. /package/dist/web/assets/{wenyan-BJFBuXuH.js → wenyan-B8plxIr8.js} +0 -0
  530. /package/dist/web/assets/{wgsl-6z4FUyMK.js → wgsl-CVujtm3V.js} +0 -0
  531. /package/dist/web/assets/{wikitext-BoDU6teJ.js → wikitext-CmcG9B69.js} +0 -0
  532. /package/dist/web/assets/{wit-DerMPpgt.js → wit--gl2Jium.js} +0 -0
  533. /package/dist/web/assets/{wolfram-D78v0Ap-.js → wolfram-6T_1D-NS.js} +0 -0
  534. /package/dist/web/assets/{x-YDu4m2Iq.js → x-CxoBA0po.js} +0 -0
  535. /package/dist/web/assets/{yaml-C4TIgIw4.js → yaml-CIVV9uA1.js} +0 -0
  536. /package/dist/web/assets/{zenscript-BydW-dLC.js → zenscript-CoT4yNdA.js} +0 -0
  537. /package/dist/web/assets/{zig-b9lTqL5E.js → zig-BWs2Qo3G.js} +0 -0
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Unified-diff parsing and selective patch building, for staging or discarding
3
+ * part of a file rather than all of it.
4
+ *
5
+ * Staging a subset of lines is not a matter of copying lines out of the diff:
6
+ * the `@@` header counts and the new-file start line both have to be recomputed,
7
+ * and an unselected deletion has to survive as *context* rather than vanish —
8
+ * otherwise the patch describes a file that never existed and `git apply`
9
+ * rejects it. That arithmetic lives here so it can be tested without a
10
+ * repository.
11
+ */
12
+
13
+ import { createHash } from "node:crypto";
14
+
15
+ export type DiffLineKind = " " | "+" | "-";
16
+
17
+ export interface DiffLine {
18
+ kind: DiffLineKind;
19
+ /** Line content without the leading +/-/space. */
20
+ text: string;
21
+ /**
22
+ * True when git emitted "" directly after this
23
+ * line. It travels with the line it belongs to.
24
+ */
25
+ noNewline?: boolean;
26
+ }
27
+
28
+ export interface DiffHunk {
29
+ oldStart: number;
30
+ oldLines: number;
31
+ newStart: number;
32
+ newLines: number;
33
+ /** Text after the closing `@@`, usually the enclosing function. */
34
+ heading: string;
35
+ lines: DiffLine[];
36
+ }
37
+
38
+ export interface ParsedDiff {
39
+ /** Everything before the first `@@`: `diff --git`, `index`, `---`, `+++`. */
40
+ header: string[];
41
+ hunks: DiffHunk[];
42
+ /** True for a diff git refused to express as text. */
43
+ binary: boolean;
44
+ }
45
+
46
+ const HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@ ?(.*)$/;
47
+
48
+ export function parseUnifiedDiff(diffText: string): ParsedDiff {
49
+ const header: string[] = [];
50
+ const hunks: DiffHunk[] = [];
51
+ let binary = false;
52
+ let current: DiffHunk | null = null;
53
+
54
+ // A trailing "\n" produces one empty element that is not part of the diff.
55
+ const lines = diffText.split("\n");
56
+ if (lines[lines.length - 1] === "") lines.pop();
57
+
58
+ for (const line of lines) {
59
+ const match = HUNK_HEADER.exec(line);
60
+ if (match) {
61
+ current = {
62
+ oldStart: Number(match[1]),
63
+ oldLines: match[2] === undefined ? 1 : Number(match[2]),
64
+ newStart: Number(match[3]),
65
+ newLines: match[4] === undefined ? 1 : Number(match[4]),
66
+ heading: match[5] ?? "",
67
+ lines: [],
68
+ };
69
+ hunks.push(current);
70
+ continue;
71
+ }
72
+
73
+ if (!current) {
74
+ if (line.startsWith("Binary files ") || line.startsWith("GIT binary patch")) {
75
+ binary = true;
76
+ }
77
+ header.push(line);
78
+ continue;
79
+ }
80
+
81
+ if (line.startsWith("\\")) {
82
+ // "" — belongs to the line just before it.
83
+ const previous = current.lines[current.lines.length - 1];
84
+ if (previous) previous.noNewline = true;
85
+ continue;
86
+ }
87
+
88
+ const kind = line[0];
89
+ if (kind === " " || kind === "+" || kind === "-") {
90
+ current.lines.push({ kind, text: line.slice(1) });
91
+ } else if (line === "") {
92
+ // git writes a bare empty line for an empty context line.
93
+ current.lines.push({ kind: " ", text: "" });
94
+ }
95
+ // Anything else (a stray "diff --git" for the next file) is ignored: callers
96
+ // pass one file's diff at a time.
97
+ }
98
+
99
+ return { header, hunks, binary };
100
+ }
101
+
102
+ /**
103
+ * Which lines of which hunks to include.
104
+ * A hunk mapped to `"all"` is taken whole; a set selects individual lines by
105
+ * their index within `hunk.lines`. Context lines are always kept regardless.
106
+ */
107
+ export type HunkSelection = Map<number, "all" | Set<number>>;
108
+
109
+ export interface BuildPatchOptions {
110
+ /**
111
+ * Build the patch so it can be applied in reverse (`git apply --reverse`),
112
+ * which is how a staged hunk is unstaged or a working-tree hunk discarded.
113
+ *
114
+ * The patch text is identical; what changes is which side is treated as
115
+ * unselected. When reversing, an unselected *addition* must become context
116
+ * (it stays in the file) and an unselected deletion is dropped — the mirror of
117
+ * the forward case.
118
+ */
119
+ reverse?: boolean;
120
+ }
121
+
122
+ /**
123
+ * Render the selected hunks as a patch, or null when the selection is empty.
124
+ */
125
+ export function buildPatch(
126
+ parsed: ParsedDiff,
127
+ selection: HunkSelection,
128
+ options: BuildPatchOptions = {},
129
+ ): string | null {
130
+ if (parsed.binary) throw new Error("A binary file cannot be staged by hunk.");
131
+
132
+ const reverse = options.reverse === true;
133
+ const out: string[] = [];
134
+ // Totals over the hunks that actually make it into the patch, which is what
135
+ // decides whether a "whole file" header is still true of it.
136
+ let oldTotal = 0;
137
+ let newTotal = 0;
138
+ // Running difference between the new and old side of the hunks included so
139
+ // far. Skipping a hunk means later hunks start at a different new-file line
140
+ // than the original diff said.
141
+ let offset = 0;
142
+
143
+ for (let h = 0; h < parsed.hunks.length; h++) {
144
+ const hunk = parsed.hunks[h]!;
145
+ const picked = selection.get(h);
146
+ if (picked === undefined) continue;
147
+
148
+ const isPicked = (index: number) => picked === "all" || picked.has(index);
149
+
150
+ const body: string[] = [];
151
+ let oldCount = 0;
152
+ let newCount = 0;
153
+
154
+ for (let i = 0; i < hunk.lines.length; i++) {
155
+ const line = hunk.lines[i]!;
156
+ const suffix = line.noNewline ? "\n\" : "";
157
+
158
+ if (line.kind === " ") {
159
+ body.push(` ${line.text}${suffix}`);
160
+ oldCount++;
161
+ newCount++;
162
+ continue;
163
+ }
164
+
165
+ const selected = isPicked(i);
166
+ // An unselected change has to be represented as whatever the *source*
167
+ // side of this patch already contains, so the patch still applies.
168
+ const keepAsContext = reverse
169
+ ? (line.kind === "+" && !selected)
170
+ : (line.kind === "-" && !selected);
171
+ const drop = reverse
172
+ ? (line.kind === "-" && !selected)
173
+ : (line.kind === "+" && !selected);
174
+
175
+ if (drop) continue;
176
+ if (keepAsContext) {
177
+ body.push(` ${line.text}${suffix}`);
178
+ oldCount++;
179
+ newCount++;
180
+ continue;
181
+ }
182
+
183
+ body.push(`${line.kind}${line.text}${suffix}`);
184
+ if (line.kind === "-") oldCount++;
185
+ else newCount++;
186
+ }
187
+
188
+ // Nothing but context left: including it would be a no-op hunk, which some
189
+ // git versions reject.
190
+ if (!body.some((l) => l.startsWith("+") || l.startsWith("-"))) continue;
191
+
192
+ // An insertion-only hunk numbers its old side as "after line N", so the new
193
+ // side starts at N+1. A new file is the extreme case: git writes `-0,0`,
194
+ // and its content begins at line 1.
195
+ const newStartBase = hunk.oldLines === 0 ? hunk.oldStart + 1 : hunk.oldStart;
196
+ // And once a narrowed selection gives that old side real content — the
197
+ // lines that were not picked, kept as context — "after line 0" has to
198
+ // become "from line 1", or the patch names a line that cannot exist.
199
+ const oldStartBase = hunk.oldLines === 0 && oldCount > 0 ? hunk.oldStart + 1 : hunk.oldStart;
200
+ out.push(formatHunkHeader(oldStartBase, oldCount, newStartBase + offset, newCount, hunk.heading));
201
+ out.push(...body);
202
+ offset += newCount - oldCount;
203
+ oldTotal += oldCount;
204
+ newTotal += newCount;
205
+ }
206
+
207
+ if (out.length === 0) return null;
208
+ return [...narrowedHeader(parsed.header, oldTotal, newTotal), ...out, ""].join("\n");
209
+ }
210
+
211
+ /**
212
+ * Downgrade a whole-file header that a partial selection has made untrue.
213
+ *
214
+ * `new file mode` says the other side of this patch is empty, and taking only
215
+ * some of a new file's lines stops that being so: the lines left behind become
216
+ * context, and git refuses the patch with "new file X depends on old contents"
217
+ * — which reads as a bug in the file rather than in the patch. `deleted file
218
+ * mode` is the same claim mirrored, and breaks the same way when only some of a
219
+ * deletion is staged. Either becomes an ordinary modification of the one path
220
+ * the header already names.
221
+ *
222
+ * The `index` line goes with them: its hashes describe the whole-file change,
223
+ * not this narrowed one. A patch without one applies the same way.
224
+ */
225
+ function narrowedHeader(header: string[], oldTotal: number, newTotal: number): string[] {
226
+ const creates = header.some((line) => line.startsWith("new file mode")) && oldTotal > 0;
227
+ const deletes = header.some((line) => line.startsWith("deleted file mode")) && newTotal > 0;
228
+ if (!creates && !deletes) return header;
229
+
230
+ const named = header.find((line) => creates ? line.startsWith("+++ b/") : line.startsWith("--- a/"));
231
+ // Without a path to name, leaving the header alone is the honest failure:
232
+ // git rejects the patch rather than applying it to something unintended.
233
+ if (!named) return header;
234
+ const path = named.slice("+++ b/".length);
235
+
236
+ return header.flatMap((line) => {
237
+ if (line.startsWith("new file mode") || line.startsWith("deleted file mode")) return [];
238
+ if (line.startsWith("index ")) return [];
239
+ if (line === "--- /dev/null") return [`--- a/${path}`];
240
+ if (line === "+++ /dev/null") return [`+++ b/${path}`];
241
+ return [line];
242
+ });
243
+ }
244
+
245
+ function formatHunkHeader(
246
+ oldStart: number,
247
+ oldLines: number,
248
+ newStart: number,
249
+ newLines: number,
250
+ heading: string,
251
+ ): string {
252
+ // The two sides are numbered differently once a range is empty.
253
+ //
254
+ // `oldStart` comes straight from git, which already wrote an empty old range
255
+ // as "the line after which content is inserted" (`-0,0` for a new file), so it
256
+ // is emitted verbatim. `newStart` is computed here as the first content line,
257
+ // so an empty new range has to step back one to mean the same thing.
258
+ const oldRange = oldLines === 1 ? `${oldStart}` : `${oldStart},${oldLines}`;
259
+ const newRange = newLines === 1
260
+ ? `${newStart}`
261
+ : `${newLines === 0 ? Math.max(0, newStart - 1) : newStart},${newLines}`;
262
+ return `@@ -${oldRange} +${newRange} @@${heading ? ` ${heading}` : ""}`;
263
+ }
264
+
265
+ /** Select every hunk whole — the "stage this file" case expressed as a patch. */
266
+ export function selectAll(parsed: ParsedDiff): HunkSelection {
267
+ const selection: HunkSelection = new Map();
268
+ for (let i = 0; i < parsed.hunks.length; i++) selection.set(i, "all");
269
+ return selection;
270
+ }
271
+
272
+ /**
273
+ * Build a selection from the shape the HTTP layer receives: a list of hunk
274
+ * indexes, each optionally narrowed to specific line indexes.
275
+ */
276
+ export function selectionFromRequest(
277
+ parsed: ParsedDiff,
278
+ requested: { hunk: number; lines?: number[] }[],
279
+ ): HunkSelection {
280
+ const selection: HunkSelection = new Map();
281
+ for (const entry of requested) {
282
+ const index = entry.hunk;
283
+ if (!Number.isInteger(index) || index < 0 || index >= parsed.hunks.length) {
284
+ throw new Error(`No hunk at index ${index}.`);
285
+ }
286
+ if (!entry.lines) {
287
+ selection.set(index, "all");
288
+ continue;
289
+ }
290
+ const hunk = parsed.hunks[index]!;
291
+ const lines = new Set<number>();
292
+ for (const line of entry.lines) {
293
+ if (!Number.isInteger(line) || line < 0 || line >= hunk.lines.length) {
294
+ throw new Error(`No line at index ${line} in hunk ${index}.`);
295
+ }
296
+ lines.add(line);
297
+ }
298
+ selection.set(index, lines);
299
+ }
300
+ return selection;
301
+ }
302
+
303
+ /**
304
+ * A content address for one hunk: everything the user was shown, and nothing
305
+ * about where it sat.
306
+ *
307
+ * Positions are deliberately excluded. An edit somewhere else in the file
308
+ * renumbers every later hunk and shifts its index in the list, but the hunk the
309
+ * user ticked is still the same change — matching on content lets that benign
310
+ * case through while a hunk whose own text moved on no longer matches anything.
311
+ *
312
+ * Computed over the bytes git emitted (see `runGit`'s latin1 decode), so it does
313
+ * not depend on the file's encoding being valid UTF-8.
314
+ */
315
+ export function hunkFingerprint(hunk: DiffHunk): string {
316
+ const body = hunk.lines
317
+ .map((line) => `${line.kind}${line.noNewline ? "\\" : ""}${line.text}`)
318
+ .join("\n");
319
+ return createHash("sha256")
320
+ .update(`${hunk.oldLines} ${hunk.newLines} ${hunk.heading}\n${body}`, "latin1")
321
+ .digest("hex")
322
+ .slice(0, 32);
323
+ }
324
+
325
+ /**
326
+ * Resolve each requested hunk to its position in `parsed` by content, refusing
327
+ * anything that is no longer there.
328
+ *
329
+ * Returns the requests rewritten onto current indexes. A caller that skipped
330
+ * this and trusted the incoming index would be resolving a position from one
331
+ * diff against a different diff — which is exactly how the wrong lines get
332
+ * staged without anything failing.
333
+ */
334
+ export function resolveRequestedHunks(
335
+ parsed: ParsedDiff,
336
+ requested: { hunk: number; id: string; lines?: number[] }[],
337
+ ): { hunk: number; lines?: number[] }[] {
338
+ const fingerprints = parsed.hunks.map(hunkFingerprint);
339
+ const taken = new Set<number>();
340
+ return requested.map((entry) => {
341
+ if (typeof entry.id !== "string" || entry.id.length === 0) {
342
+ throw new Error("This selection is from an older client that cannot be verified — reload and try again.");
343
+ }
344
+ const candidates: number[] = [];
345
+ for (let i = 0; i < fingerprints.length; i++) {
346
+ if (fingerprints[i] === entry.id && !taken.has(i)) candidates.push(i);
347
+ }
348
+ if (candidates.length === 0) {
349
+ throw new Error(
350
+ "The file changed since these hunks were listed, so this selection no longer describes it. Reload and try again.",
351
+ );
352
+ }
353
+ // The same edit can appear twice in one file; prefer the position the
354
+ // client actually saw it at before falling back to the first free match.
355
+ const index = candidates.includes(entry.hunk) ? entry.hunk : candidates[0]!;
356
+ taken.add(index);
357
+ return entry.lines ? { hunk: index, lines: entry.lines } : { hunk: index };
358
+ });
359
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Binding a client-named account to a session.
3
+ *
4
+ * A chat tab claims an account when it opens and shows its name before anything is sent.
5
+ * When the first message finally creates the session, the tab hands that id back so the
6
+ * turn runs on the account the user was looking at rather than on whatever the strategy
7
+ * would pick a moment later.
8
+ *
9
+ * The id arrives from a browser, so it is treated as a request rather than an instruction:
10
+ * it is checked against the server's own pool and quietly declined if it does not hold up.
11
+ * Declining is deliberately silent — the session is already created and the message is
12
+ * about to be sent, and failing the whole request because a stale account id came along
13
+ * would turn a cosmetic mismatch into a lost message. The turn simply routes normally.
14
+ */
15
+
16
+ import { accountSelector } from "./account-selector.service.ts";
17
+ import { getCodexAccount } from "./codex-account.service.ts";
18
+ import { setSessionAccount, setSessionCodexAccount } from "./db.service.ts";
19
+
20
+ /** Whether this provider/account pair is one the server would route to on its own. */
21
+ export function canBindAccount(providerId: string, accountId: string): boolean {
22
+ if (providerId === "codex") {
23
+ const acc = getCodexAccount(accountId);
24
+ return acc !== null && acc.status !== "disabled";
25
+ }
26
+ return accountSelector.canServe(accountId);
27
+ }
28
+
29
+ /**
30
+ * Why a bind was refused, in words the panel can show as-is.
31
+ *
32
+ * Worth the extra call: "that account cannot serve this session" tells a user nothing they
33
+ * can act on, while "it has reached its 5-hour limit, resets at 4:10pm" tells them whether
34
+ * to wait or pick another.
35
+ */
36
+ export function bindRefusalReason(providerId: string, accountId: string): string {
37
+ if (providerId === "codex") {
38
+ const acc = getCodexAccount(accountId);
39
+ if (!acc) return "That account no longer exists.";
40
+ return acc.status === "disabled" ? "That account is switched off." : "That account cannot serve this session.";
41
+ }
42
+ return accountSelector.refusalReason(accountId) ?? "That account cannot serve this session.";
43
+ }
44
+
45
+ /**
46
+ * Bind a client-named account to a session, if the server agrees it can serve.
47
+ *
48
+ * Returns whether the binding was written, so a caller that can report back to a user
49
+ * (the manual picker) can say the choice did not take, while a caller that cannot
50
+ * (session creation) can ignore it.
51
+ */
52
+ export function bindPickedAccount(sessionId: string, providerId: string, accountId: string): boolean {
53
+ if (!canBindAccount(providerId, accountId)) return false;
54
+ if (providerId === "codex") setSessionCodexAccount(sessionId, accountId);
55
+ else setSessionAccount(sessionId, accountId);
56
+ return true;
57
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Incremental read of one teammate's transcript, for the member window's poll.
3
+ *
4
+ * A teammate transcript reaches several MB, so the window cannot re-download the
5
+ * whole file every few seconds just to learn that three steps were appended. The
6
+ * client keeps the byte offset it has consumed and asks only for what is past
7
+ * it — the same tail mechanism `nested-subagent-spy.ts` uses for live cards,
8
+ * expressed as one stateless call so an HTTP poll can drive it.
9
+ */
10
+
11
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
12
+ import type { ChatEvent } from "../../types/chat.ts";
13
+ import { createAgentTranscriptLineParser } from "../subagent-transcript-merger.ts";
14
+
15
+ export interface MemberTranscriptSlice {
16
+ /** Events from the requested offset onward, in file order. */
17
+ events: ChatEvent[];
18
+ /** Offset the next poll should resume from — always a line boundary. */
19
+ nextBytes: number;
20
+ /** The caller's offset no longer fits the file; `events` is the whole file. */
21
+ restarted: boolean;
22
+ }
23
+
24
+ /** Read the bytes a caller has not seen yet and parse them into child events. */
25
+ export function readMemberTranscriptSlice(filePath: string, sinceBytes: number): MemberTranscriptSlice {
26
+ let size: number;
27
+ try {
28
+ size = statSync(filePath).size;
29
+ } catch {
30
+ return { events: [], nextBytes: sinceBytes, restarted: false };
31
+ }
32
+
33
+ // A shrunk file was rotated or rewritten: the offset is meaningless, start over.
34
+ const restarted = sinceBytes > size;
35
+ const start = restarted || sinceBytes < 0 ? 0 : sinceBytes;
36
+ if (start >= size) return { events: [], nextBytes: start, restarted: false };
37
+
38
+ let buf: Buffer;
39
+ const fd = openSync(filePath, "r");
40
+ try {
41
+ buf = Buffer.alloc(size - start);
42
+ const read = readSync(fd, buf, 0, buf.length, start);
43
+ if (read < buf.length) buf = buf.subarray(0, read);
44
+ } catch {
45
+ return { events: [], nextBytes: start, restarted: false };
46
+ } finally {
47
+ closeSync(fd);
48
+ }
49
+
50
+ // Stop at the last newline: a half-written record must be re-read next poll,
51
+ // and cutting the buffer on a byte boundary keeps split multi-byte chars whole.
52
+ const lastNewline = buf.lastIndexOf(0x0a);
53
+ if (lastNewline < 0) return { events: [], nextBytes: start, restarted };
54
+
55
+ // A resumed read never begins at the spawn prompt, but the parser's skip only
56
+ // ever drops a user record carrying no tool_result — which emits nothing either
57
+ // way — so the same factory is correct at any offset.
58
+ const parser = createAgentTranscriptLineParser();
59
+ const events: ChatEvent[] = [];
60
+ for (const line of buf.subarray(0, lastNewline + 1).toString("utf8").split("\n")) {
61
+ for (const timed of parser.feed(line)) events.push(timed.ev);
62
+ }
63
+ return { events, nextBytes: start + lastNewline + 1, restarted };
64
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The shape `GET /api/projects/:name/git/blame` returns, and the formatting the
3
+ * editor's inline annotation needs. Shared because the server produces it and
4
+ * the frontend renders it.
5
+ */
6
+
7
+ export interface BlameCommitInfo {
8
+ hash: string;
9
+ author: string;
10
+ authorMail: string;
11
+ /** Unix seconds. */
12
+ authorTime: number;
13
+ summary: string;
14
+ /** Path the file had at this commit — differs after a rename. */
15
+ filename?: string;
16
+ /** The commit this line came from before it, when git could work it out. */
17
+ previous?: string;
18
+ }
19
+
20
+ export interface BlameLine {
21
+ hash: string;
22
+ /** 1-based line number in the file as it is now. */
23
+ finalLine: number;
24
+ /** 1-based line number in the file at that commit. */
25
+ origLine: number;
26
+ }
27
+
28
+ export interface BlameResult {
29
+ /** In final-line order, so `lines[n - 1]` is normally line n. */
30
+ lines: BlameLine[];
31
+ commits: Record<string, BlameCommitInfo>;
32
+ }
33
+
34
+ /** git uses an all-zero sha for lines that are not committed yet. */
35
+ export const UNCOMMITTED_HASH = "0".repeat(40);
36
+
37
+ export function isUncommittedHash(hash: string): boolean {
38
+ return /^0+$/.test(hash);
39
+ }
40
+
41
+ /**
42
+ * The one-line annotation shown after the cursor's line, in GitLens' shape:
43
+ * "Author, 3 days ago • summary".
44
+ *
45
+ * `now` is a parameter rather than read from the clock so the wording can be
46
+ * tested without freezing time.
47
+ */
48
+ export function formatBlameAnnotation(
49
+ commit: BlameCommitInfo | undefined | null,
50
+ now: number = Date.now(),
51
+ ): string {
52
+ if (!commit) return "";
53
+ if (isUncommittedHash(commit.hash)) return "You, uncommitted changes";
54
+ const who = commit.author || "Unknown";
55
+ const when = commit.authorTime > 0 ? formatRelativeTime(commit.authorTime * 1000, now) : "";
56
+ const head = when ? `${who}, ${when}` : who;
57
+ return commit.summary ? `${head} • ${commit.summary}` : head;
58
+ }
59
+
60
+ /** Coarse relative time. Anything past a year reads as a year count. */
61
+ export function formatRelativeTime(timestampMs: number, now: number = Date.now()): string {
62
+ const seconds = Math.max(0, Math.round((now - timestampMs) / 1000));
63
+ if (seconds < 60) return "just now";
64
+
65
+ const units: [limit: number, size: number, name: string][] = [
66
+ [3600, 60, "minute"],
67
+ [86400, 3600, "hour"],
68
+ [2592000, 86400, "day"],
69
+ [31536000, 2592000, "month"],
70
+ [Infinity, 31536000, "year"],
71
+ ];
72
+ for (const [limit, size, name] of units) {
73
+ if (seconds < limit) {
74
+ const n = Math.floor(seconds / size);
75
+ return `${n} ${name}${n === 1 ? "" : "s"} ago`;
76
+ }
77
+ }
78
+ return "just now";
79
+ }
@@ -37,6 +37,16 @@ interface ChatHistoryBarProps {
37
37
  lastFetchedAt?: string | null;
38
38
  sessionId?: string | null;
39
39
  providerId?: string;
40
+ /**
41
+ * Account this tab claimed before it had a session, so the chip can name who will answer
42
+ * the first message instead of leaving a blank until after it is sent. Once the session
43
+ * exists its binding takes over and this goes unused.
44
+ */
45
+ pickedAccountLabel?: string | null;
46
+ /** Id of that same account, so the panel can mark which card is serving this chat. */
47
+ pickedAccountId?: string | null;
48
+ /** Route this chat onto another account. */
49
+ onSelectAccount?: (accountId: string, label: string | null) => Promise<string | null>;
40
50
  onSelectSession?: (session: SessionInfo) => void;
41
51
  onBugReport?: () => void;
42
52
  isConnected?: boolean;
@@ -63,7 +73,8 @@ function pctColor(pct: number): string {
63
73
 
64
74
  export function ChatHistoryBar({
65
75
  projectName, usageInfo, usageLoading, refreshUsage, lastFetchedAt,
66
- sessionId, providerId, onSelectSession, onBugReport, isConnected, onReload,
76
+ sessionId, providerId, pickedAccountLabel, pickedAccountId, onSelectAccount,
77
+ onSelectSession, onBugReport, isConnected, onReload,
67
78
  teamActivity, teamMessages, onTeamOpen,
68
79
  }: ChatHistoryBarProps) {
69
80
  const [activePanel, setActivePanel] = useState<PanelType>(null);
@@ -272,6 +283,12 @@ export function ChatHistoryBar({
272
283
  const sevenDayPct = usageInfo.sevenDay != null ? Math.round(usageInfo.sevenDay * 100) : null;
273
284
  const worstPct = Math.max(fiveHourPct ?? 0, sevenDayPct ?? 0);
274
285
  const usageColor = fiveHourPct != null || sevenDayPct != null ? pctColor(worstPct) : "text-text-subtle";
286
+ // Order matters, and the obvious order is wrong. With no session, the usage endpoint has
287
+ // no session to scope to and answers with whichever account ran last across every chat —
288
+ // so letting it win would show a name that has nothing to do with this tab, which is the
289
+ // exact confusion the claim exists to remove. The claim (and, once a turn is running, the
290
+ // account the stream reports) is specific to this chat and outranks it.
291
+ const accountLabel = pickedAccountLabel ?? usageInfo.activeAccountLabel ?? null;
275
292
 
276
293
  return (
277
294
  <div className="border-b border-border/50">
@@ -324,16 +341,20 @@ export function ChatHistoryBar({
324
341
  title="Usage limits"
325
342
  >
326
343
  <Activity className="size-3" />
327
- {usageInfo.activeAccountLabel && (
344
+ {accountLabel && (
328
345
  // Which account is serving this session, per provider. An email
329
346
  // label is unreadable cut to 60px ("hienlh1298@…"), and the
330
347
  // account is the thing a multi-account user checks here, so it
331
348
  // gets the room and the full string on hover.
349
+ //
350
+ // Before the first message there is no session and so no binding to
351
+ // report; the tab's claimed account stands in, and it is the same one
352
+ // that message will run on.
332
353
  <span
333
354
  className="text-text-secondary font-normal truncate max-w-[110px]"
334
- title={usageInfo.activeAccountLabel}
355
+ title={accountLabel}
335
356
  >
336
- [{usageInfo.activeAccountLabel}]
357
+ [{accountLabel}]
337
358
  </span>
338
359
  )}
339
360
  <span>5h:{fiveHourPct != null ? `${fiveHourPct}%` : "--%"}</span>
@@ -620,6 +641,8 @@ export function ChatHistoryBar({
620
641
  {activePanel === "usage" && isClaudeProvider && (
621
642
  <UsageDetailPanel
622
643
  usage={usageInfo}
644
+ onSelectAccount={onSelectAccount}
645
+ selectedAccountId={pickedAccountId ?? usageInfo.activeAccountId ?? null}
623
646
  visible={true}
624
647
  onClose={() => setActivePanel(null)}
625
648
  onReload={refreshUsage}
@@ -628,7 +651,13 @@ export function ChatHistoryBar({
628
651
  />
629
652
  )}
630
653
  {activePanel === "usage" && isCodexProvider && (
631
- <CodexUsagePanel usage={usageInfo} onClose={() => setActivePanel(null)} onReload={refreshUsage} />
654
+ <CodexUsagePanel
655
+ usage={usageInfo}
656
+ onClose={() => setActivePanel(null)}
657
+ onReload={refreshUsage}
658
+ onSelectAccount={onSelectAccount}
659
+ selectedAccountId={pickedAccountId ?? usageInfo.activeAccountId ?? null}
660
+ />
632
661
  )}
633
662
 
634
663
  </div>