@hienlh/ppm 0.19.11 → 0.20.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 (536) 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-BQyru31C.js +1 -0
  6. package/dist/web/assets/{ai-resource-editor-iMqzzz5c.js → ai-resource-editor-BgYIPGVP.js} +1 -1
  7. package/dist/web/assets/{ai-settings-section-B-1z-PRb.js → ai-settings-section-Cywmtnps.js} +1 -1
  8. package/dist/web/assets/ai-settings-section-iBRCQvBR.js +1 -0
  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-2XZgB0mV.js → appearance-settings-section-mhu9T6Fk.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-Bs9OD6ss.js +15 -0
  24. package/dist/web/assets/{cobol-Bo0usdoM.js → cobol-BreMVrtc.js} +1 -1
  25. package/dist/web/assets/code-editor-Cv_pzHo7.js +7 -0
  26. package/dist/web/assets/{coffee-DBBsbX1e.js → coffee-D7DoKh5b.js} +1 -1
  27. package/dist/web/assets/{conflict-editor-BpdoTEfv.js → conflict-editor-BnWaMSEa.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-C3DoHISX.js → csv-preview-DG4YAabr.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-CxZ2fdcd.js → database-viewer-B-wA2qT-.js} +1 -1
  34. package/dist/web/assets/diff-viewer-DRtzdfXk.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-6tzdk7Pn.js} +1 -1
  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-Bpe6UX0b.js → explorer-body-uh8oJp6J.js} +2 -2
  44. package/dist/web/assets/explorer-window-content-DLn_4egF.js +1 -0
  45. package/dist/web/assets/{extension-manager-section-D_rKLp-o.js → extension-manager-section-C_ZGYvWf.js} +1 -1
  46. package/dist/web/assets/{extension-webview-kEtrZoRZ.js → extension-webview-DbFWxJpj.js} +1 -1
  47. package/dist/web/assets/{files-settings-section-B0wLfQ9S.js → files-settings-section-BrUbJglA.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-DZ3xpgwh.js → general-settings-section-B9ZPZVso.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-BNicD2In.js → git-log-panel-C0jnhmgC.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-CdK5dW5L.js → glide-data-grid-CZVKIum-.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-DnPtmbPc.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-CHQAPZIX.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-DL_boZNh.js → jira-watcher-section-Bjldyhru.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-DBVXZKp8.js +1 -0
  80. package/dist/web/assets/{keyboard-shortcuts-section-DzQzV49E.js → keyboard-shortcuts-section-D7jaCPrv.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-BfoCOrL8.js → markdown-renderer-B4B5oxQS.js} +3 -3
  85. package/dist/web/assets/markdown-renderer-DwHh-jd_.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-JJxZYWkV.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-CCWNUHsS.js +1 -0
  95. package/dist/web/assets/{notifications-settings-section-BXCaGBVc.js → notifications-settings-section-DbbX7-Qp.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-DbRFOtQD.js → pdf-preview-DxNjWa1v.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-BrQRqTso.js → postgres-viewer-BScvk5ev.js} +3 -3
  105. package/dist/web/assets/{ppmbot-settings-section-BEV23_SM.js → ppmbot-settings-section-jFA9Ncgo.js} +1 -1
  106. package/dist/web/assets/{process-table-CG8d5eXs.js → process-table-BexeYWQm.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-Cw0Wtsv0.js → proxy-settings-section-C_seKq56.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-50B7mQal.js → query-audit-section-BWXJmPDV.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-B--PeL5i.js +2 -0
  115. package/dist/web/assets/{remote-desktop-mobile-view-DPF_b_jd.js → remote-desktop-mobile-view-jgn8nmQa.js} +1 -1
  116. package/dist/web/assets/{remote-desktop-readiness-gate-J7E6355i.js → remote-desktop-readiness-gate-CjDth-W_.js} +1 -1
  117. package/dist/web/assets/{remote-desktop-window-content-0xsAifZH.js → remote-desktop-window-content-CrHAUA03.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-DMFn3mYL.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-CUTsPLea.js → settings-body-Dz2vPTEP.js} +2 -2
  127. package/dist/web/assets/settings-store-CzLnTR8p.js +2 -0
  128. package/dist/web/assets/settings-tab-B5QbsvS4.js +1 -0
  129. package/dist/web/assets/settings-window-content-iM2bPoX5.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-CwOcDbp4.js → sql-query-editor-C0mGgLfX.js} +1 -1
  135. package/dist/web/assets/{sqlite-viewer-DYdcqZv1.js → sqlite-viewer-Bs3RKf1X.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-CugdEvdh.js → system-monitor-body-DvUmEdNe.js} +2 -2
  141. package/dist/web/assets/system-monitor-tab-CHZ5F_ZE.js +1 -0
  142. package/dist/web/assets/{system-monitor-window-content-CJfcQqc2.js → system-monitor-window-content-DjzqoeCN.js} +1 -1
  143. package/dist/web/assets/{tab-host-window-content-5Ga7BheG.js → tab-host-window-content-DKeAr5mD.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-Cl9ZB3dl.js +2 -0
  146. package/dist/web/assets/team-member-window-content-DfP7WdFD.js +1 -0
  147. package/dist/web/assets/{templ-BVHE2CNb.js → templ-BAdNyIOL.js} +1 -1
  148. package/dist/web/assets/{terminal-tab-CkxJIjii.js → terminal-tab-J1Fqa_rm.js} +2 -2
  149. package/dist/web/assets/{tex-C8XwrbBw.js → tex-IQ8mELOR.js} +1 -1
  150. package/dist/web/assets/tool-cards-DQghSuKX.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-KWcMgRlc.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-DOhDvEKJ.js → use-monaco-theme-aa4-wjKQ.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-B3VIBQxC.js → use-remote-desktop-display-choice-B-TpQ-Ua.js} +1 -1
  160. package/dist/web/assets/{use-websocket-D9MsDysx.js → use-websocket-BBslmv3D.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-VL2Br0YM.js → video-preview-Bd0lcK3d.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 +35 -6
  230. package/src/web/components/chat/chat-tab.tsx +120 -3
  231. package/src/web/components/chat/codex-usage-panel.tsx +148 -14
  232. package/src/web/components/chat/team-member-window-content.tsx +8 -39
  233. package/src/web/components/chat/usage-badge.tsx +176 -52
  234. package/src/web/components/editor/code-editor.tsx +33 -1
  235. package/src/web/components/editor/diff-viewer.tsx +83 -8
  236. package/src/web/components/editor/editor-toolbar.tsx +25 -1
  237. package/src/web/components/git/git-status-panel.tsx +46 -0
  238. package/src/web/components/git/hunk-selection.ts +132 -0
  239. package/src/web/components/git/hunk-stage-dialog.tsx +419 -0
  240. package/src/web/components/settings/accounts/account-bucket-row.tsx +19 -3
  241. package/src/web/components/settings/accounts/account-card.tsx +153 -33
  242. package/src/web/components/settings/accounts/account-hint.tsx +33 -0
  243. package/src/web/components/settings/accounts/account-usage-format.ts +26 -0
  244. package/src/web/components/settings/accounts/codex-accounts-section.tsx +24 -9
  245. package/src/web/components/settings/accounts/codex-usage-rows.tsx +2 -2
  246. package/src/web/components/settings/accounts/use-accounts-data.ts +32 -2
  247. package/src/web/components/settings/accounts/use-codex-accounts.ts +24 -2
  248. package/src/web/hooks/use-chat.ts +27 -0
  249. package/src/web/hooks/use-inline-blame.ts +175 -0
  250. package/src/web/hooks/use-member-transcript-tail.ts +95 -0
  251. package/src/web/hooks/use-usage.ts +3 -3
  252. package/src/web/lib/api-settings.ts +24 -0
  253. package/src/web/stores/settings-store.ts +12 -0
  254. package/dist/web/assets/accounts-settings-section-C1YP1iCb.js +0 -1
  255. package/dist/web/assets/ai-settings-section-BJPgHUqk.js +0 -1
  256. package/dist/web/assets/api-client-BogqsFna.js +0 -1
  257. package/dist/web/assets/api-settings-BVfemzZW.js +0 -1
  258. package/dist/web/assets/architecture-PBZL5I3N-BZBxtErN.js +0 -1
  259. package/dist/web/assets/chat-tab-Bn4vZVZJ.js +0 -15
  260. package/dist/web/assets/code-editor-DN5GJXT3.js +0 -7
  261. package/dist/web/assets/diff-viewer-DxzD_YSO.js +0 -4
  262. package/dist/web/assets/explorer-window-content-DixZaLLP.js +0 -1
  263. package/dist/web/assets/gitGraph-HDMCJU4V-BHxJbXfV.js +0 -1
  264. package/dist/web/assets/group-chat-tab-D-HXdnoU.js +0 -1
  265. package/dist/web/assets/index-DdMq4zqb.js +0 -52
  266. package/dist/web/assets/index-N-pnuwFP.css +0 -2
  267. package/dist/web/assets/info-3K5VOQVL-DI4p3vbH.js +0 -1
  268. package/dist/web/assets/keybindings-store-DQkz4a35.js +0 -1
  269. package/dist/web/assets/markdown-renderer-C35QMvZp.js +0 -1
  270. package/dist/web/assets/mobile-explorer-sheet-ZHfT5IN3.js +0 -1
  271. package/dist/web/assets/notification-store-J1yP_Ebb.js +0 -1
  272. package/dist/web/assets/packet-RMMSAZCW-YNQWTi_-.js +0 -1
  273. package/dist/web/assets/pie-UPGHQEXC-E3Wiw76n.js +0 -1
  274. package/dist/web/assets/radar-KQ55EAFF-BPQJUKON.js +0 -1
  275. package/dist/web/assets/remote-desktop-mobile-sheet-DhcAyXE5.js +0 -2
  276. package/dist/web/assets/schedules-settings-section-BFzMEPTd.js +0 -1
  277. package/dist/web/assets/settings-store-DWUx3865.js +0 -2
  278. package/dist/web/assets/settings-tab-DVV26vPK.js +0 -1
  279. package/dist/web/assets/settings-window-content-z64J3tfm.js +0 -1
  280. package/dist/web/assets/system-monitor-tab-jnPQtmvK.js +0 -1
  281. package/dist/web/assets/team-member-sheet-8FDCVZMG.js +0 -2
  282. package/dist/web/assets/team-member-window-content-0JRiWRdG.js +0 -1
  283. package/dist/web/assets/tool-cards-DuTlfeWN.js +0 -9
  284. package/dist/web/assets/treemap-KZPCXAKY-C2CvKBQR.js +0 -1
  285. package/dist/web/assets/use-accounts-data--pdg01TI.js +0 -1
  286. package/dist/web/assets/ws-auth-ZSZkAT6p.js +0 -1
  287. /package/dist/web/assets/{abap-Cq9nYQyd.js → abap-D-o-bvtI.js} +0 -0
  288. /package/dist/web/assets/{actionscript-3-DL8rUIy0.js → actionscript-3-D2tIzAm8.js} +0 -0
  289. /package/dist/web/assets/{ada-BY8TLuhY.js → ada-DxatBcxS.js} +0 -0
  290. /package/dist/web/assets/{andromeeda-CyVfHsS0.js → andromeeda-D0LK9YJ3.js} +0 -0
  291. /package/dist/web/assets/{apache-DTQUy73x.js → apache-B7uMPNiW.js} +0 -0
  292. /package/dist/web/assets/{apex-CiSZngrZ.js → apex-B7GiXrAr.js} +0 -0
  293. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-CEYCBt_0.js} +0 -0
  294. /package/dist/web/assets/{applescript-CzuRDd3h.js → applescript-D_SIFkKe.js} +0 -0
  295. /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-BVXAUYoN.js} +0 -0
  296. /package/dist/web/assets/{ara-CSFV93kR.js → ara-C3iECgdg.js} +0 -0
  297. /package/dist/web/assets/{asciidoc-hOQNnXhz.js → asciidoc-gc8z_MMY.js} +0 -0
  298. /package/dist/web/assets/{asm-DdG53NGR.js → asm-27qeoPOI.js} +0 -0
  299. /package/dist/web/assets/{aurora-x-DJ7ORZG6.js → aurora-x-C5J9fWe1.js} +0 -0
  300. /package/dist/web/assets/{awk-B3FFpX_J.js → awk-B7GBzF92.js} +0 -0
  301. /package/dist/web/assets/{ayu-dark-D_P1CfTe.js → ayu-dark-XlnQhuLL.js} +0 -0
  302. /package/dist/web/assets/{ayu-light-Cz3YLUqH.js → ayu-light-DKLLilkU.js} +0 -0
  303. /package/dist/web/assets/{ayu-mirage-JcMq5tgJ.js → ayu-mirage-Dv-XOprJ.js} +0 -0
  304. /package/dist/web/assets/{ballerina-7Hs4Rfpk.js → ballerina-DACRT-sT.js} +0 -0
  305. /package/dist/web/assets/{bat-COgQdnFA.js → bat-BlQCq2XE.js} +0 -0
  306. /package/dist/web/assets/{beancount-BJ3CutBG.js → beancount-DYDztRI0.js} +0 -0
  307. /package/dist/web/assets/{berry-D7so2Cui.js → berry-BE5YKqvx.js} +0 -0
  308. /package/dist/web/assets/{bibtex-Ct3acHa1.js → bibtex-CAm7mgEm.js} +0 -0
  309. /package/dist/web/assets/{bicep-jXDwCQcm.js → bicep-BKgAbfVa.js} +0 -0
  310. /package/dist/web/assets/{bird2-CjLD2cna.js → bird2-yrBNbW1G.js} +0 -0
  311. /package/dist/web/assets/{c-CdgZojnL.js → c-txZFr4RM.js} +0 -0
  312. /package/dist/web/assets/{c3-DtpZYego.js → c3-D_DrzBUd.js} +0 -0
  313. /package/dist/web/assets/{cadence-CPLkixoh.js → cadence-CUFCEgG9.js} +0 -0
  314. /package/dist/web/assets/{can-open-in-ppm-BntPywsy.js → can-open-in-ppm-_yqa69dV.js} +0 -0
  315. /package/dist/web/assets/{catppuccin-frappe-Xt0_qJa9.js → catppuccin-frappe-Dn32MuhA.js} +0 -0
  316. /package/dist/web/assets/{catppuccin-latte-CJnbLec5.js → catppuccin-latte-Dewo7yDf.js} +0 -0
  317. /package/dist/web/assets/{catppuccin-macchiato-BPHP1wYB.js → catppuccin-macchiato-D8WOWZeo.js} +0 -0
  318. /package/dist/web/assets/{catppuccin-mocha-ssWJodGc.js → catppuccin-mocha-b0md48XM.js} +0 -0
  319. /package/dist/web/assets/{clarity-DB0408F6.js → clarity-Ct3UqLBj.js} +0 -0
  320. /package/dist/web/assets/{clojure-dpRPlAO4.js → clojure-CwP5aPEo.js} +0 -0
  321. /package/dist/web/assets/{cmake-r2VkeX9y.js → cmake-D5RF49e5.js} +0 -0
  322. /package/dist/web/assets/{codeowners-v5nQqHiB.js → codeowners-Cl5897L_.js} +0 -0
  323. /package/dist/web/assets/{codeql-BSSEhKRR.js → codeql-BKh1RCg0.js} +0 -0
  324. /package/dist/web/assets/{common-lisp-BXb2MMMA.js → common-lisp-BhHuD6t0.js} +0 -0
  325. /package/dist/web/assets/{coq-BjBNi73X.js → coq-Dco3qwD8.js} +0 -0
  326. /package/dist/web/assets/{csharp-CdPjZRbV.js → csharp-Dpjw9ZA9.js} +0 -0
  327. /package/dist/web/assets/{css-BuxbQOHL.js → css-CPe4cabH.js} +0 -0
  328. /package/dist/web/assets/{csv-Dj-eoZwH.js → csv-BAcTN8Kt.js} +0 -0
  329. /package/dist/web/assets/{csv-parser-CBbALsFU.js → csv-parser-DbxGKdMG.js} +0 -0
  330. /package/dist/web/assets/{cue-Dz73VksN.js → cue-Cr6y60TA.js} +0 -0
  331. /package/dist/web/assets/{cypher-CkVkDywZ.js → cypher-DWtqXXFP.js} +0 -0
  332. /package/dist/web/assets/{d-oU51Pbpf.js → d-BASiTZk4.js} +0 -0
  333. /package/dist/web/assets/{dark-plus-DcwvjjfO.js → dark-plus-56kmnrcl.js} +0 -0
  334. /package/dist/web/assets/{dart-DGvu8mcX.js → dart-C5mZnjoE.js} +0 -0
  335. /package/dist/web/assets/{data-grid-types-B6kUrF-M.js → data-grid-types-DeY60CLU.js} +0 -0
  336. /package/dist/web/assets/{dax-BmbjKQ90.js → dax-B4I2e7d8.js} +0 -0
  337. /package/dist/web/assets/{desktop-IRBSiFfh.js → desktop-DekGg0BT.js} +0 -0
  338. /package/dist/web/assets/{diff-Bdik2JR8.js → diff-CWnJBd73.js} +0 -0
  339. /package/dist/web/assets/{dist-Bv4zQpS1.js → dist-B0J2eYBz.js} +0 -0
  340. /package/dist/web/assets/{dist-C8YPBIOb.js → dist-BKOM_AoT.js} +0 -0
  341. /package/dist/web/assets/{docker-Bgdl0H8I.js → docker-DevchDxK.js} +0 -0
  342. /package/dist/web/assets/{dotenv-COe5NdnA.js → dotenv-DCvtdaGJ.js} +0 -0
  343. /package/dist/web/assets/{dracula-DjURaSAa.js → dracula-8U6Vkk3l.js} +0 -0
  344. /package/dist/web/assets/{dracula-soft-Do7d0bo-.js → dracula-soft-Bfeew5Wg.js} +0 -0
  345. /package/dist/web/assets/{dream-maker-DYwQvJIj.js → dream-maker-DDTJVuoy.js} +0 -0
  346. /package/dist/web/assets/{emacs-lisp-BXjlHyDW.js → emacs-lisp-CEHJqI4m.js} +0 -0
  347. /package/dist/web/assets/{everforest-dark-h8Q2loiX.js → everforest-dark-CHHLt7cq.js} +0 -0
  348. /package/dist/web/assets/{everforest-light-CdsJiL-9.js → everforest-light-TcTbK1sM.js} +0 -0
  349. /package/dist/web/assets/{fennel-D0CBtOvY.js → fennel-Bh4vsA1A.js} +0 -0
  350. /package/dist/web/assets/{fish-DzayfrMj.js → fish-D0T0fILG.js} +0 -0
  351. /package/dist/web/assets/{fluent-CatjkUfx.js → fluent-CgXDXdyy.js} +0 -0
  352. /package/dist/web/assets/{format-bytes-BulRSULX.js → format-bytes-Bc6SqF7M.js} +0 -0
  353. /package/dist/web/assets/{fortran-free-form-DM04ud0b.js → fortran-free-form-CUg3Xhz4.js} +0 -0
  354. /package/dist/web/assets/{gdscript-BLN5iZwf.js → gdscript-Cx056p_k.js} +0 -0
  355. /package/dist/web/assets/{gdshader-BK-1iZQs.js → gdshader-Dj1qP4I5.js} +0 -0
  356. /package/dist/web/assets/{genie-BGPOFWDN.js → genie-SBt15wn7.js} +0 -0
  357. /package/dist/web/assets/{gherkin-CdVOJ_JY.js → gherkin-bTJiHIrB.js} +0 -0
  358. /package/dist/web/assets/{github-dark-B_xW8TaY.js → github-dark-BOPhyugs.js} +0 -0
  359. /package/dist/web/assets/{github-dark-default-h_iKq7CX.js → github-dark-default-Cktqe6NF.js} +0 -0
  360. /package/dist/web/assets/{github-dark-dimmed-nKDYE62T.js → github-dark-dimmed-CeWfII_B.js} +0 -0
  361. /package/dist/web/assets/{github-dark-high-contrast-l8I771QT.js → github-dark-high-contrast-CTx8LpdV.js} +0 -0
  362. /package/dist/web/assets/{github-light-DNhjkg8Z.js → github-light-DVsO5Un1.js} +0 -0
  363. /package/dist/web/assets/{github-light-default-DQ06uS3g.js → github-light-default-BiyPxicr.js} +0 -0
  364. /package/dist/web/assets/{github-light-high-contrast-qc2sZCVN.js → github-light-high-contrast-BPlnke0a.js} +0 -0
  365. /package/dist/web/assets/{gleam-DPPatkNH.js → gleam-CWSxvD0C.js} +0 -0
  366. /package/dist/web/assets/{gn-BnzGQTy5.js → gn-D7NUVJuv.js} +0 -0
  367. /package/dist/web/assets/{gnuplot-Ct1leoo3.js → gnuplot-c5uXmJKc.js} +0 -0
  368. /package/dist/web/assets/{go-qysCdbrn.js → go-CRsHi4Cw.js} +0 -0
  369. /package/dist/web/assets/{groovy-BkWIdrRQ.js → groovy-DpV0RA5r.js} +0 -0
  370. /package/dist/web/assets/{gruvbox-dark-hard-L6Iiybbl.js → gruvbox-dark-hard-DD9fguUN.js} +0 -0
  371. /package/dist/web/assets/{gruvbox-dark-medium-DbD3yl0b.js → gruvbox-dark-medium-BvcwpYJ-.js} +0 -0
  372. /package/dist/web/assets/{gruvbox-dark-soft-Ban2cHK3.js → gruvbox-dark-soft-48dCxX9M.js} +0 -0
  373. /package/dist/web/assets/{gruvbox-light-hard-CK_SjS3e.js → gruvbox-light-hard-CjJ994Ip.js} +0 -0
  374. /package/dist/web/assets/{gruvbox-light-medium-Bjgvhc2w.js → gruvbox-light-medium-DQJQZ_rV.js} +0 -0
  375. /package/dist/web/assets/{gruvbox-light-soft-EWnyYGUu.js → gruvbox-light-soft-AqsC9mKw.js} +0 -0
  376. /package/dist/web/assets/{haskell-rT1N9nYr.js → haskell-B5V7yvmh.js} +0 -0
  377. /package/dist/web/assets/{haxe-Bbc91axo.js → haxe-LWTKfPKr.js} +0 -0
  378. /package/dist/web/assets/{hcl-D-3qJA4v.js → hcl-QmNZdAtI.js} +0 -0
  379. /package/dist/web/assets/{hjson-DWtewcyQ.js → hjson-CboJsAzK.js} +0 -0
  380. /package/dist/web/assets/{hlsl-C7t6hCrO.js → hlsl-Bd-DO_WL.js} +0 -0
  381. /package/dist/web/assets/{horizon-zJAGUQ3j.js → horizon-CU8Bi_ss.js} +0 -0
  382. /package/dist/web/assets/{horizon-bright-CRNyrJy8.js → horizon-bright-iSQ3_3jP.js} +0 -0
  383. /package/dist/web/assets/{houston-BFKa4W1-.js → houston-CTXv3Puz.js} +0 -0
  384. /package/dist/web/assets/{hy-W1hNjMBM.js → hy-Cd4hTidD.js} +0 -0
  385. /package/dist/web/assets/{image-gallery-BycaVWOu.js → image-gallery-J8h8IY0c.js} +0 -0
  386. /package/dist/web/assets/{imba-CPphaV-V.js → imba-BipCHEY2.js} +0 -0
  387. /package/dist/web/assets/{ini-CayaZ4vW.js → ini-Dd1KkMrB.js} +0 -0
  388. /package/dist/web/assets/{java-BPAE_9Ej.js → java-rGr0OQD8.js} +0 -0
  389. /package/dist/web/assets/{javascript-C5SKIVqe.js → javascript-DHhCH2TN.js} +0 -0
  390. /package/dist/web/assets/{json-C-0EUYmu.js → json-CMMLBCUf.js} +0 -0
  391. /package/dist/web/assets/{json5-BqVetEZL.js → json5-mPOwTOsB.js} +0 -0
  392. /package/dist/web/assets/{jsonc-DnjIzkUB.js → jsonc-Bie0E6D3.js} +0 -0
  393. /package/dist/web/assets/{jsonl-rbV-3EqO.js → jsonl-CpqHmtC1.js} +0 -0
  394. /package/dist/web/assets/{jsonnet-9hvXw4qr.js → jsonnet-D21Ms51e.js} +0 -0
  395. /package/dist/web/assets/{jssm-1ifXyIhu.js → jssm-BXnl1nur.js} +0 -0
  396. /package/dist/web/assets/{jsx-hPumywTY.js → jsx-Chnii_vB.js} +0 -0
  397. /package/dist/web/assets/{kanagawa-dragon-BCPjmoMO.js → kanagawa-dragon-DhxR0UjL.js} +0 -0
  398. /package/dist/web/assets/{kanagawa-lotus-DN2VNjy7.js → kanagawa-lotus-fupRg5wp.js} +0 -0
  399. /package/dist/web/assets/{kanagawa-wave-B8RZEsgc.js → kanagawa-wave-DSWbRS9M.js} +0 -0
  400. /package/dist/web/assets/{kdl-CkvjWQIa.js → kdl-DirGHqUy.js} +0 -0
  401. /package/dist/web/assets/{kotlin-D8cnRi5o.js → kotlin-DkhUVMDC.js} +0 -0
  402. /package/dist/web/assets/{kusto-CmeeBEJG.js → kusto-DU7OpovF.js} +0 -0
  403. /package/dist/web/assets/{laserwave--rtV8Mv9.js → laserwave-iLKb1ZCT.js} +0 -0
  404. /package/dist/web/assets/{lean-CS-Pyi42.js → lean-Cuvh_QAP.js} +0 -0
  405. /package/dist/web/assets/{less-Ddi_7qGm.js → less-B5tQZOdI.js} +0 -0
  406. /package/dist/web/assets/{lib-DIrxSyLb.js → lib-el9rA30D.js} +0 -0
  407. /package/dist/web/assets/{libesm-CYLrAeW3.js → libesm-CdttTIAd.js} +0 -0
  408. /package/dist/web/assets/{light-plus-CrEjpkkX.js → light-plus-DiHU2KL5.js} +0 -0
  409. /package/dist/web/assets/{llvm-Cm7ZXmAO.js → llvm-sCBTKCKN.js} +0 -0
  410. /package/dist/web/assets/{log-lW2sYxtp.js → log-DtyR34Ie.js} +0 -0
  411. /package/dist/web/assets/{logo-B-PiRb53.js → logo-CP91aQyG.js} +0 -0
  412. /package/dist/web/assets/{luau-8St0pC-R.js → luau-nfGo2ulP.js} +0 -0
  413. /package/dist/web/assets/{make-DbNdhH54.js → make-XbDn9GHe.js} +0 -0
  414. /package/dist/web/assets/{markdown-CYocYSGd.js → markdown-BB1_3xLr.js} +0 -0
  415. /package/dist/web/assets/{material-theme-CVBF1Cbi.js → material-theme-CIx_sxAy.js} +0 -0
  416. /package/dist/web/assets/{material-theme-darker-HFrlNZD8.js → material-theme-darker-DsQeueDD.js} +0 -0
  417. /package/dist/web/assets/{material-theme-lighter-BM-uz2ft.js → material-theme-lighter-x_lMi74j.js} +0 -0
  418. /package/dist/web/assets/{material-theme-ocean-PbvUez8m.js → material-theme-ocean-Bkgh25P8.js} +0 -0
  419. /package/dist/web/assets/{material-theme-palenight-B22Ck6qd.js → material-theme-palenight-CoOjdbti.js} +0 -0
  420. /package/dist/web/assets/{matlab-GFFNzucw.js → matlab-D6xZas_8.js} +0 -0
  421. /package/dist/web/assets/{mdx-BVnJzMDx.js → mdx-Cl5_YNoz.js} +0 -0
  422. /package/dist/web/assets/{mermaid-BlCmSTCM.js → mermaid-8Eow4jz_.js} +0 -0
  423. /package/dist/web/assets/{min-dark-B0PwlAfC.js → min-dark-D4DhjgRE.js} +0 -0
  424. /package/dist/web/assets/{min-light-C_H2ABKu.js → min-light-DzAckd0t.js} +0 -0
  425. /package/dist/web/assets/{mipsasm-C3rf2fo6.js → mipsasm-zI_zu0tj.js} +0 -0
  426. /package/dist/web/assets/{mojo-CDRuzM3o.js → mojo-DQnGGPHf.js} +0 -0
  427. /package/dist/web/assets/{monokai-KiebNy_Y.js → monokai-CrFoD2VD.js} +0 -0
  428. /package/dist/web/assets/{moonbit-CdVUHoWl.js → moonbit-DdphIGs6.js} +0 -0
  429. /package/dist/web/assets/{move-Dq9iKZRY.js → move-BUlkn0xW.js} +0 -0
  430. /package/dist/web/assets/{narrat-7y5L5sGM.js → narrat-CbOBdtCn.js} +0 -0
  431. /package/dist/web/assets/{nextflow-groovy-Cwcbkcrx.js → nextflow-groovy-CeVLUTuQ.js} +0 -0
  432. /package/dist/web/assets/{night-owl-TBgLPzj4.js → night-owl-Bm4LhUR1.js} +0 -0
  433. /package/dist/web/assets/{night-owl-light-Dehb9My2.js → night-owl-light-DtcBiMEf.js} +0 -0
  434. /package/dist/web/assets/{nix-BKj-gzoX.js → nix-DrdwQ70g.js} +0 -0
  435. /package/dist/web/assets/{nord-BF_r8PeX.js → nord-B0Head1v.js} +0 -0
  436. /package/dist/web/assets/{nushell-CeAjk9_E.js → nushell-CmUdltKn.js} +0 -0
  437. /package/dist/web/assets/{objective-c-Xl1OMcDp.js → objective-c-BGDR33XN.js} +0 -0
  438. /package/dist/web/assets/{objective-cpp-C4ZgzrRq.js → objective-cpp-Buc1Zhd0.js} +0 -0
  439. /package/dist/web/assets/{ocaml-CGj8IYTq.js → ocaml-xdzl9rRD.js} +0 -0
  440. /package/dist/web/assets/{odin-C4veqvW2.js → odin-DiUFngnd.js} +0 -0
  441. /package/dist/web/assets/{one-dark-pro-gAcWejsV.js → one-dark-pro-BURxmIp3.js} +0 -0
  442. /package/dist/web/assets/{one-light-B7E-UUXy.js → one-light-BMRMInAj.js} +0 -0
  443. /package/dist/web/assets/{openscad-C-wN9BEv.js → openscad-DAgLA5J3.js} +0 -0
  444. /package/dist/web/assets/{pascal-C8WBtxMs.js → pascal-CoDZxcRB.js} +0 -0
  445. /package/dist/web/assets/{pkl-BBfF4G2A.js → pkl-DzKM6BuM.js} +0 -0
  446. /package/dist/web/assets/{plastic-DFOAb89P.js → plastic-vF15LmNS.js} +0 -0
  447. /package/dist/web/assets/{plsql-BS2CEMMV.js → plsql-C1v07teA.js} +0 -0
  448. /package/dist/web/assets/{po-BKJDtPmy.js → po-CLLC8BuA.js} +0 -0
  449. /package/dist/web/assets/{poimandres-Cg3hL13e.js → poimandres-Uq0xysxM.js} +0 -0
  450. /package/dist/web/assets/{polar-B9ebiXUU.js → polar-2-luY1Pc.js} +0 -0
  451. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DKymYOY-.js} +0 -0
  452. /package/dist/web/assets/{postcss-B5REwNY_.js → postcss-lbFhe0rz.js} +0 -0
  453. /package/dist/web/assets/{powerquery-stc0Gzkf.js → powerquery-RL8O9Wie.js} +0 -0
  454. /package/dist/web/assets/{powershell-jgd0ahJN.js → powershell-upFWhb-b.js} +0 -0
  455. /package/dist/web/assets/{prisma-CWuZjRhI.js → prisma-DBtYaWFk.js} +0 -0
  456. /package/dist/web/assets/{prolog-D5zNFE63.js → prolog-D8jSMykJ.js} +0 -0
  457. /package/dist/web/assets/{proto-B-6RrU7n.js → proto-BM4eadF7.js} +0 -0
  458. /package/dist/web/assets/{puppet-DONP8TDR.js → puppet-BQDQ3Snu.js} +0 -0
  459. /package/dist/web/assets/{purescript-DDJITf-A.js → purescript-Cj2aTX4s.js} +0 -0
  460. /package/dist/web/assets/{python-bmWy9j1-.js → python-BYpqq6u4.js} +0 -0
  461. /package/dist/web/assets/{qmldir-CKDwMt7X.js → qmldir-4uiTcwAu.js} +0 -0
  462. /package/dist/web/assets/{qss-DptQQzBn.js → qss-D5xB-Mqo.js} +0 -0
  463. /package/dist/web/assets/{r-BorKpJ8r.js → r-P6Ujy8rf.js} +0 -0
  464. /package/dist/web/assets/{racket-BaHpYVsZ.js → racket-COKgCFuM.js} +0 -0
  465. /package/dist/web/assets/{raku-DFJIV4k_.js → raku-z14Q9i7K.js} +0 -0
  466. /package/dist/web/assets/{react-X4jUntEf.js → react-D1Yg256j.js} +0 -0
  467. /package/dist/web/assets/{red-70oJGrkb.js → red-ObAvgvQ5.js} +0 -0
  468. /package/dist/web/assets/{reg-C4g235Dv.js → reg-Qv4f0JFA.js} +0 -0
  469. /package/dist/web/assets/{regexp-Cw1UVmti.js → regexp-CEOVGIzy.js} +0 -0
  470. /package/dist/web/assets/{rel-DeLhIz5L.js → rel-cWOyTUwd.js} +0 -0
  471. /package/dist/web/assets/{resolve-theme-b_yGlUFQ.js → resolve-theme-CMp5xxCW.js} +0 -0
  472. /package/dist/web/assets/{riscv-ChnTiBRK.js → riscv-BZDwx-lv.js} +0 -0
  473. /package/dist/web/assets/{ron-C5IWeqDn.js → ron-BpjosPS0.js} +0 -0
  474. /package/dist/web/assets/{rose-pine-DCR0ohYW.js → rose-pine-DhxfJWcm.js} +0 -0
  475. /package/dist/web/assets/{rose-pine-dawn-DvGZs1PR.js → rose-pine-dawn-BbGt9Wy_.js} +0 -0
  476. /package/dist/web/assets/{rose-pine-moon-BWACHjeS.js → rose-pine-moon-dj4Zxgik.js} +0 -0
  477. /package/dist/web/assets/{rosmsg-CQlIVazE.js → rosmsg-EciOaHo2.js} +0 -0
  478. /package/dist/web/assets/{rust-DGGVzWI8.js → rust-C_eH59O_.js} +0 -0
  479. /package/dist/web/assets/{sass-bj1C0rFI.js → sass-Dos3R5lL.js} +0 -0
  480. /package/dist/web/assets/{scala-MU1tLKDi.js → scala-8lVKsTvf.js} +0 -0
  481. /package/dist/web/assets/{scheme-CeGXtv7t.js → scheme-Bd3VdaZj.js} +0 -0
  482. /package/dist/web/assets/{sdbl-D0HLSGKp.js → sdbl-C4Yrjlgh.js} +0 -0
  483. /package/dist/web/assets/{shellscript-DkYiDvh1.js → shellscript-OFZbPKZH.js} +0 -0
  484. /package/dist/web/assets/{slack-dark-YqeHiGWH.js → slack-dark-aL6vlxZs.js} +0 -0
  485. /package/dist/web/assets/{slack-ochin-9Oe0DljJ.js → slack-ochin-BKt1E7sL.js} +0 -0
  486. /package/dist/web/assets/{smalltalk-CaaxedR7.js → smalltalk-D6qVBL1U.js} +0 -0
  487. /package/dist/web/assets/{snazzy-light-C8s2FHNC.js → snazzy-light-BrWerT0t.js} +0 -0
  488. /package/dist/web/assets/{solarized-dark-0ryY6oN3.js → solarized-dark-CzxTJgVQ.js} +0 -0
  489. /package/dist/web/assets/{solarized-light-bltgZeNh.js → solarized-light-BecaDAnG.js} +0 -0
  490. /package/dist/web/assets/{solidity-DOf5HmLZ.js → solidity-C9t7pZ84.js} +0 -0
  491. /package/dist/web/assets/{splunk-D1u4WHDW.js → splunk-CtxfFKtF.js} +0 -0
  492. /package/dist/web/assets/{sql-G8uQZljX.js → sql-7FE_Duz0.js} +0 -0
  493. /package/dist/web/assets/{ssh-config-rbXvly7Q.js → ssh-config-3Fr37ACC.js} +0 -0
  494. /package/dist/web/assets/{stylus-Cii1iADy.js → stylus-BP4mE1sO.js} +0 -0
  495. /package/dist/web/assets/{swift-DpIgonN4.js → swift-0uqTFouD.js} +0 -0
  496. /package/dist/web/assets/{synthwave-84-DjGLzU2b.js → synthwave-84-DQaNM3DI.js} +0 -0
  497. /package/dist/web/assets/{system-verilog-_Z0M0Hs1.js → system-verilog-DUIVrZHZ.js} +0 -0
  498. /package/dist/web/assets/{systemd-BhyDCh9P.js → systemd-CtNo_WIh.js} +0 -0
  499. /package/dist/web/assets/{talonscript-CNchgCRD.js → talonscript-B0HXaCVT.js} +0 -0
  500. /package/dist/web/assets/{tasl-m--W7Pyr.js → tasl-Cb7RAl11.js} +0 -0
  501. /package/dist/web/assets/{tcl-gY-M_2BC.js → tcl-B79aC6l5.js} +0 -0
  502. /package/dist/web/assets/{terraform-CB6CzMwo.js → terraform-75jJDjpW.js} +0 -0
  503. /package/dist/web/assets/{tokyo-night-COh9oRYi.js → tokyo-night-Cz9Uh9qb.js} +0 -0
  504. /package/dist/web/assets/{toml-Bg2bsY-v.js → toml-CD6y9A-z.js} +0 -0
  505. /package/dist/web/assets/{tsv-DceGDiJC.js → tsv-CXJMbBrb.js} +0 -0
  506. /package/dist/web/assets/{tsx-EccDYa7i.js → tsx-BuYN0I2t.js} +0 -0
  507. /package/dist/web/assets/{turtle-C_l2JhvX.js → turtle-DWH-Rjse.js} +0 -0
  508. /package/dist/web/assets/{typescript-C1ytmMcr.js → typescript-BPd-hsrR.js} +0 -0
  509. /package/dist/web/assets/{typespec-De2ztzG5.js → typespec-B0wvVykx.js} +0 -0
  510. /package/dist/web/assets/{typst-DNcS7gSr.js → typst-DWG2c48-.js} +0 -0
  511. /package/dist/web/assets/{use-is-mobile-CYW3FJFE.js → use-is-mobile-D1weanbj.js} +0 -0
  512. /package/dist/web/assets/{use-visual-viewport-D9O48HQt.js → use-visual-viewport-Bfl3Bxxb.js} +0 -0
  513. /package/dist/web/assets/{utils-C9OjCrou.js → utils-IrnQujwR.js} +0 -0
  514. /package/dist/web/assets/{v-DSMUsdMO.js → v-Ch6E4Egd.js} +0 -0
  515. /package/dist/web/assets/{vala-BEazLpkO.js → vala-CGVKwbPx.js} +0 -0
  516. /package/dist/web/assets/{vb-CV9pEJzW.js → vb-BIEbSxyT.js} +0 -0
  517. /package/dist/web/assets/{vendor-xterm-cuGQmUjN.js → vendor-xterm-YQlzIWv2.js} +0 -0
  518. /package/dist/web/assets/{verilog-DhXkLVMk.js → verilog-A-3pYPad.js} +0 -0
  519. /package/dist/web/assets/{vesper-n4IltYq-.js → vesper-CK1naZAV.js} +0 -0
  520. /package/dist/web/assets/{vhdl-CePjFbvs.js → vhdl-CVawmgMg.js} +0 -0
  521. /package/dist/web/assets/{viml-ChndFsm5.js → viml-CiWjaqBY.js} +0 -0
  522. /package/dist/web/assets/{vitesse-black-BytVdvhC.js → vitesse-black-PYXW4-BO.js} +0 -0
  523. /package/dist/web/assets/{vitesse-dark-83Wech0N.js → vitesse-dark-BU5s4AuG.js} +0 -0
  524. /package/dist/web/assets/{vitesse-light-Xg4wbeMO.js → vitesse-light-DgkIT-N7.js} +0 -0
  525. /package/dist/web/assets/{vyper-eW1NC3FG.js → vyper-D8cRDPp3.js} +0 -0
  526. /package/dist/web/assets/{wasm-BucxKwBZ.js → wasm-C02wlRbP.js} +0 -0
  527. /package/dist/web/assets/{wasm-Bhucqu1z.js → wasm-ClFJgzmT.js} +0 -0
  528. /package/dist/web/assets/{wenyan-BJFBuXuH.js → wenyan-B8plxIr8.js} +0 -0
  529. /package/dist/web/assets/{wgsl-6z4FUyMK.js → wgsl-CVujtm3V.js} +0 -0
  530. /package/dist/web/assets/{wikitext-BoDU6teJ.js → wikitext-CmcG9B69.js} +0 -0
  531. /package/dist/web/assets/{wit-DerMPpgt.js → wit--gl2Jium.js} +0 -0
  532. /package/dist/web/assets/{wolfram-D78v0Ap-.js → wolfram-6T_1D-NS.js} +0 -0
  533. /package/dist/web/assets/{x-YDu4m2Iq.js → x-CxoBA0po.js} +0 -0
  534. /package/dist/web/assets/{yaml-C4TIgIw4.js → yaml-CIVV9uA1.js} +0 -0
  535. /package/dist/web/assets/{zenscript-BydW-dLC.js → zenscript-CoT4yNdA.js} +0 -0
  536. /package/dist/web/assets/{zig-b9lTqL5E.js → zig-BWs2Qo3G.js} +0 -0
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * The chat header's usage chip and the panel it opens.
3
3
  *
4
- * Display only. Adding, removing, enabling, exporting and rotating accounts all live in
5
- * Settings → Accounts; this panel links there instead of carrying its own copy of those
6
- * controls, which is what let the two drift apart. The account cards are the same component
7
- * the Settings pane renders — passing no action callbacks is what makes them read-only, so
8
- * there is one card implementation rather than a display twin.
4
+ * Adding, removing, exporting and rotating accounts live in Settings → Accounts; this panel
5
+ * links there rather than carrying its own copy of them. What it does carry is the two
6
+ * decisions that belong to the conversation in front of you: which account serves it, and
7
+ * whether an account is available at all.
8
+ *
9
+ * The rule the original separation was protecting still holds, and is what makes those two
10
+ * safe to have here: the cards are the same component the Settings pane renders, and the
11
+ * switch drives the same `useAccountsData().toggle` that pane drives. One implementation, two
12
+ * places it appears — not a display twin that can drift.
9
13
  *
10
14
  * Accounts sit in a row that scrolls sideways, not a vertical stack: this panel exists to
11
15
  * compare them, and stacked in a 350px strip that meant scrolling past one account to see
@@ -15,11 +19,17 @@
15
19
  import { useState } from "react";
16
20
  import { Activity, ExternalLink, Maximize2, Minimize2, RefreshCw, X } from "lucide-react";
17
21
  import type { UsageInfo } from "../../../types/chat";
22
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
18
23
  import { openSettings } from "@/components/settings/open-settings";
19
24
  import { AccountCard } from "@/components/settings/accounts/account-card";
20
25
  import { AccountBucketRow } from "@/components/settings/accounts/account-bucket-row";
21
26
  import { useAccountsData } from "@/components/settings/accounts/use-accounts-data";
22
- import { formatLastUpdated, pctColor } from "@/components/settings/accounts/account-usage-format";
27
+ import { formatLastUpdated, formatResetTime, pctColor } from "@/components/settings/accounts/account-usage-format";
28
+
29
+ /** Matches the whole-percent figure the usage bars show, so a refusal agrees with the card. */
30
+ function atCap(util: number | null | undefined): boolean {
31
+ return Math.round((util ?? 0) * 100) >= 100;
32
+ }
23
33
 
24
34
  interface UsageBadgeProps {
25
35
  usage: UsageInfo;
@@ -38,16 +48,20 @@ export function UsageBadge({ usage, loading, onClick }: UsageBadgeProps) {
38
48
  const colorClass = fiveHourPct != null || sevenDayPct != null ? pctColor(worstPct) : "text-text-subtle";
39
49
 
40
50
  return (
41
- <button
42
- onClick={onClick}
43
- className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-hover ${colorClass}`}
44
- title="Click for usage details"
45
- >
46
- {loading ? <RefreshCw className="size-3 animate-spin" /> : <Activity className="size-3" />}
47
- <span>5h:{fiveHourLabel}</span>
48
- <span className="text-text-subtle">·</span>
49
- <span>Wk:{sevenDayLabel}</span>
50
- </button>
51
+ <Tooltip>
52
+ <TooltipTrigger asChild>
53
+ <button
54
+ onClick={onClick}
55
+ className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-hover ${colorClass}`}
56
+ >
57
+ {loading ? <RefreshCw className="size-3 animate-spin" /> : <Activity className="size-3" />}
58
+ <span>5h:{fiveHourLabel}</span>
59
+ <span className="text-text-subtle">·</span>
60
+ <span>Wk:{sevenDayLabel}</span>
61
+ </button>
62
+ </TooltipTrigger>
63
+ <TooltipContent side="top">Click for usage details</TooltipContent>
64
+ </Tooltip>
51
65
  );
52
66
  }
53
67
 
@@ -60,17 +74,89 @@ interface UsageDetailPanelProps {
60
74
  onReload?: () => void;
61
75
  loading?: boolean;
62
76
  lastFetchedAt?: string | null;
77
+ /** Route this chat to a different account. Omitted when the caller cannot re-route. */
78
+ onSelectAccount?: (accountId: string, label: string | null) => Promise<string | null>;
79
+ /** Account currently claimed or bound for this chat, so the card can mark itself. */
80
+ selectedAccountId?: string | null;
63
81
  }
64
82
 
65
- export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, lastFetchedAt }: UsageDetailPanelProps) {
83
+ export function UsageDetailPanel({
84
+ usage, visible, onClose, onReload, loading, lastFetchedAt,
85
+ onSelectAccount, selectedAccountId,
86
+ }: UsageDetailPanelProps) {
87
+ const [selectingId, setSelectingId] = useState<string | null>(null);
66
88
  // Fetching is gated on visibility: the panel is collapsed most of the time, and its
67
89
  // usage endpoint is the expensive one.
68
- const { usages, accounts, activeAccountId, initialLoading, refreshing, flashIds, reload } = useAccountsData(visible);
90
+ const { usages, accounts, activeAccountId, initialLoading, refreshing, flashIds, reload, toggle, togglingId } = useAccountsData(visible);
91
+ // One strip for anything that went wrong in this panel — a refused switch or a refused
92
+ // toggle. Two separate messages in two places would be harder to notice, not clearer.
93
+ const [panelError, setPanelError] = useState<string | null>(null);
69
94
  const [isFullscreen, setIsFullscreen] = useState(false);
70
95
 
71
96
  if (!visible) return null;
72
97
 
73
98
  const accountMap = new Map(accounts.map((a) => [a.id, a]));
99
+
100
+ /**
101
+ * Why an account cannot take this chat, in the words the card shows.
102
+ *
103
+ * Only the reasons the server itself refuses — disabled, or a token past saving. Being
104
+ * near a quota cap is not one of them: that is a slower turn, not an impossible one, and
105
+ * a user who picks a nearly-capped account on purpose is allowed to.
106
+ */
107
+ function unselectableReason(
108
+ info?: (typeof accounts)[number],
109
+ entry?: (typeof usages)[number],
110
+ ): string | null {
111
+ if (info) {
112
+ if (info.status === "disabled") return "Disabled";
113
+ // A rejected grant is refused before the token's freshness is even consulted, so such
114
+ // an account cannot take a turn however healthy the rest of the card looks.
115
+ if (info.reauthRequired) return "Sign in again to use this account";
116
+ const expired = !info.hasRefreshToken && info.expiresAt && info.expiresAt < Math.floor(Date.now() / 1000);
117
+ if (expired) return "Token expired — sign in again";
118
+ }
119
+ // Reached, not merely approaching. The router steers away from 95% so it can spread load
120
+ // early, but an account at 96% still answers — refusing it would be wrong. At 100% the
121
+ // next turn fails outright, and that is the point where saying no is the truth.
122
+ const session = entry?.usage.session;
123
+ if (atCap(session?.utilization)) {
124
+ return `5-hour limit reached${session?.resetsAt ? ` — resets ${formatResetTime(session)}` : ""}`;
125
+ }
126
+ const weekly = entry?.usage.weekly;
127
+ if (atCap(weekly?.utilization)) {
128
+ return `Weekly limit reached${weekly?.resetsAt ? ` — resets ${formatResetTime(weekly)}` : ""}`;
129
+ }
130
+ return null;
131
+ }
132
+
133
+ async function handleToggle(id: string, status: string) {
134
+ setPanelError(await toggle(id, status));
135
+ }
136
+
137
+ async function handleSelect(accountId: string) {
138
+ if (!onSelectAccount) return;
139
+ // Refuse here rather than leaving the button inert: the user pressed something and is
140
+ // owed the reason. Checked again server-side, which is what catches an account that
141
+ // became unusable between this render and the click.
142
+ const refused = unselectableReason(
143
+ accountMap.get(accountId),
144
+ usages.find((u) => u.accountId === accountId),
145
+ );
146
+ if (refused) { setPanelError(`Cannot switch to this account — ${refused.toLowerCase()}.`); return; }
147
+
148
+ setSelectingId(accountId);
149
+ setPanelError(null);
150
+ try {
151
+ // The label travels with the id: a tab that has no session yet displays the choice
152
+ // straight away, and it is the only place that knows the human-readable name.
153
+ const label = usages.find((u) => u.accountId === accountId)?.accountLabel ?? null;
154
+ setPanelError((await onSelectAccount(accountId, label)) ?? null);
155
+ } finally {
156
+ setSelectingId(null);
157
+ }
158
+ }
159
+
74
160
  const hasCost = usage.queryCostUsd != null || usage.totalCostUsd != null;
75
161
  const hasPerAccountUsage = usages.length > 0;
76
162
 
@@ -95,33 +181,45 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
95
181
  )}
96
182
  </div>
97
183
  <div className="flex items-center gap-1">
98
- <button
99
- onClick={() => openSettings("accounts")}
100
- className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-primary px-1 cursor-pointer"
101
- title="Add, remove or rotate accounts"
102
- >
103
- Manage accounts <ExternalLink className="size-3" />
104
- </button>
184
+ <Tooltip>
185
+ <TooltipTrigger asChild>
186
+ <button
187
+ onClick={() => openSettings("accounts")}
188
+ className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-primary px-1 cursor-pointer"
189
+ >
190
+ Manage accounts <ExternalLink className="size-3" />
191
+ </button>
192
+ </TooltipTrigger>
193
+ <TooltipContent side="top">Add, remove or rotate accounts</TooltipContent>
194
+ </Tooltip>
105
195
  {hasPerAccountUsage && (
106
- <button
107
- onClick={() => setIsFullscreen((v) => !v)}
108
- className="text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer"
109
- title={isFullscreen ? "Exit fullscreen" : "Fullscreen view"}
110
- aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen view"}
111
- >
112
- {isFullscreen ? <Minimize2 className="size-3" /> : <Maximize2 className="size-3" />}
113
- </button>
196
+ <Tooltip>
197
+ <TooltipTrigger asChild>
198
+ <button
199
+ onClick={() => setIsFullscreen((v) => !v)}
200
+ className="text-xs text-text-subtle hover:text-text-primary px-1 cursor-pointer"
201
+ aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen view"}
202
+ >
203
+ {isFullscreen ? <Minimize2 className="size-3" /> : <Maximize2 className="size-3" />}
204
+ </button>
205
+ </TooltipTrigger>
206
+ <TooltipContent side="top">{isFullscreen ? "Exit fullscreen" : "Fullscreen view"}</TooltipContent>
207
+ </Tooltip>
114
208
  )}
115
209
  {onReload && (
116
- <button
117
- onClick={() => { onReload(); void reload(); }}
118
- disabled={loading || refreshing}
119
- className="text-xs text-text-subtle hover:text-text-primary px-1 disabled:opacity-50 cursor-pointer"
120
- title="Refresh"
121
- aria-label="Refresh usage"
122
- >
123
- <RefreshCw className={`size-3 ${(loading || refreshing) ? "animate-spin" : ""}`} />
124
- </button>
210
+ <Tooltip>
211
+ <TooltipTrigger asChild>
212
+ <button
213
+ onClick={() => { onReload(); void reload(); }}
214
+ disabled={loading || refreshing}
215
+ className="text-xs text-text-subtle hover:text-text-primary px-1 disabled:opacity-50 cursor-pointer"
216
+ aria-label="Refresh usage"
217
+ >
218
+ <RefreshCw className={`size-3 ${(loading || refreshing) ? "animate-spin" : ""}`} />
219
+ </button>
220
+ </TooltipTrigger>
221
+ <TooltipContent side="top">Refresh</TooltipContent>
222
+ </Tooltip>
125
223
  )}
126
224
  <button
127
225
  onClick={() => { setIsFullscreen(false); onClose(); }}
@@ -133,6 +231,22 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
133
231
  </div>
134
232
  </div>
135
233
 
234
+ {/* The server distinguishes "this login was rejected, sign in again" from "could not
235
+ reach Anthropic, try shortly", and the difference decides what the user should do.
236
+ Showing its words verbatim is the only way that survives to them. */}
237
+ {panelError && (
238
+ <div className="shrink-0 flex items-start gap-2 rounded border border-error/40 bg-error/10 px-2 py-1.5 text-[11px] text-error">
239
+ <span className="flex-1">{panelError}</span>
240
+ <button
241
+ onClick={() => setPanelError(null)}
242
+ className="shrink-0 text-error/70 hover:text-error cursor-pointer"
243
+ aria-label="Dismiss"
244
+ >
245
+ <X className="size-3" />
246
+ </button>
247
+ </div>
248
+ )}
249
+
136
250
  {hasPerAccountUsage || initialLoading ? (
137
251
  <div
138
252
  className={isFullscreen
@@ -147,16 +261,26 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
147
261
  {initialLoading ? (
148
262
  <p className="text-[10px] text-text-subtle">Loading...</p>
149
263
  ) : (
150
- usages.map((entry) => (
151
- <AccountCard
152
- key={entry.accountId}
153
- entry={entry}
154
- isActive={entry.accountId === (activeAccountId ?? usage.activeAccountId)}
155
- accountInfo={accountMap.get(entry.accountId)}
156
- flash={flashIds.has(entry.accountId)}
157
- layout={isFullscreen ? "grid" : "strip"}
158
- />
159
- ))
264
+ usages.map((entry) => {
265
+ const info = accountMap.get(entry.accountId);
266
+ return (
267
+ <AccountCard
268
+ key={entry.accountId}
269
+ entry={entry}
270
+ // What is serving THIS chat, not what the rotation would pick next —
271
+ // those differ the moment a session is bound to an account.
272
+ isActive={entry.accountId === (selectedAccountId ?? usage.activeAccountId ?? activeAccountId)}
273
+ accountInfo={info}
274
+ flash={flashIds.has(entry.accountId)}
275
+ layout={isFullscreen ? "grid" : "strip"}
276
+ onSelect={onSelectAccount ? handleSelect : undefined}
277
+ unselectableReason={unselectableReason(info, entry)}
278
+ selecting={selectingId === entry.accountId}
279
+ onToggle={handleToggle}
280
+ toggling={togglingId === entry.accountId}
281
+ />
282
+ );
283
+ })
160
284
  )}
161
285
  </div>
162
286
  ) : usage.session || usage.weekly || usage.weeklyOpus || usage.weeklySonnet ? (
@@ -8,6 +8,7 @@ import { usePanelStore } from "@/stores/panel-store";
8
8
  import { useSettingsStore } from "@/stores/settings-store";
9
9
  import { basename } from "@/lib/utils";
10
10
  import { useMonacoTheme } from "@/lib/use-monaco-theme";
11
+ import { useInlineBlame } from "@/hooks/use-inline-blame";
11
12
  import { Loader2, FileWarning, Play, Database, ExternalLink, X, GripHorizontal, ShieldCheck, ShieldOff } from "lucide-react";
12
13
  import { EditorBreadcrumb } from "./editor-breadcrumb";
13
14
  import { EditorToolbar } from "./editor-toolbar";
@@ -73,8 +74,16 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
73
74
  const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
74
75
  const latestContentRef = useRef<string>("");
75
76
  const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);
77
+ // Mirrors editorRef as state, so hooks that must react to the editor existing
78
+ // (inline blame) re-run on mount instead of reading a ref that is still null.
79
+ const [mounted, setMounted] = useState<{
80
+ editor: MonacoType.editor.IStandaloneCodeEditor;
81
+ monaco: typeof MonacoType;
82
+ } | null>(null);
76
83
  const { tabs, updateTab } = useTabStore(useShallow((s) => ({ tabs: s.tabs, updateTab: s.updateTab })));
77
84
  const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));
85
+ const inlineBlame = useSettingsStore((s) => s.inlineBlame);
86
+ const toggleInlineBlame = useSettingsStore((s) => s.toggleInlineBlame);
78
87
  const monacoTheme = useMonacoTheme();
79
88
 
80
89
  const isUntitled = metadata?.isUntitled === true;
@@ -367,6 +376,18 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
367
376
  if (ownTab.title !== newTitle) updateTab(ownTab.id, { title: newTitle });
368
377
  }, [unsaved]); // eslint-disable-line react-hooks/exhaustive-deps
369
378
 
379
+ // GitLens-style annotation on the cursor's line. Off unless the pref is on,
380
+ // and never for an untitled buffer or inline (read-only preview) content —
381
+ // neither has a path git could blame.
382
+ const canBlame = !isUntitled && inlineContent == null && !!filePath && !!projectName;
383
+ const blame = useInlineBlame({
384
+ editor: mounted?.editor ?? null,
385
+ monaco: mounted?.monaco ?? null,
386
+ projectName,
387
+ filePath: canBlame ? filePath : undefined,
388
+ enabled: inlineBlame && canBlame,
389
+ });
390
+
370
391
  const saveFile = useCallback(
371
392
  async (text: string) => {
372
393
  if (!filePath) return;
@@ -378,9 +399,12 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
378
399
  await api.put(`${projectUrl(projectName!)}/files/write`, { path: filePath, content: text });
379
400
  }
380
401
  setUnsaved(false);
402
+ // The blame line table is keyed by line number, so an edit invalidates
403
+ // it; a save is the point where a fresh one can be had.
404
+ blame.refresh();
381
405
  } catch { /* Silent — unsaved indicator persists */ }
382
406
  },
383
- [filePath, projectName, isExternalFile],
407
+ [filePath, projectName, isExternalFile, blame.refresh], // eslint-disable-line react-hooks/exhaustive-deps
384
408
  );
385
409
 
386
410
  function handleChange(value: string | undefined) {
@@ -452,6 +476,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
452
476
  const handleEditorMount: OnMount = useCallback((editor, monaco) => {
453
477
  editorRef.current = editor;
454
478
  monacoInstanceRef.current = monaco;
479
+ setMounted({ editor, monaco });
455
480
  if (lineNumber && lineNumber > 0) {
456
481
  setTimeout(() => revealTarget(), 100);
457
482
  }
@@ -466,6 +491,10 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
466
491
  monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,
467
492
  () => useSettingsStore.getState().toggleWordWrap(),
468
493
  );
494
+ editor.addCommand(
495
+ monaco.KeyMod.Alt | monaco.KeyCode.KeyB,
496
+ () => useSettingsStore.getState().toggleInlineBlame(),
497
+ );
469
498
  monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
470
499
  noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,
471
500
  });
@@ -694,6 +723,9 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
694
723
  onCsvModeChange={setCsvMode}
695
724
  wordWrap={wordWrap}
696
725
  onToggleWordWrap={toggleWordWrap}
726
+ inlineBlame={inlineBlame}
727
+ onToggleInlineBlame={canBlame ? toggleInlineBlame : undefined}
728
+ blameStale={blame.stale}
697
729
  onRefresh={reloadFile}
698
730
  refreshing={refreshing}
699
731
  filePath={filePath}
@@ -5,7 +5,8 @@ import { useShallow } from "zustand/react/shallow";
5
5
  import { useSettingsStore } from "@/stores/settings-store";
6
6
  import { useMonacoTheme } from "@/lib/use-monaco-theme";
7
7
  import { onHostResize } from "@/components/floating-window/pip/pip-resize-signal";
8
- import { Loader2, FileCode, WrapText } from "lucide-react";
8
+ import { Loader2, FileCode, WrapText, UserRound } from "lucide-react";
9
+ import { useInlineBlame } from "@/hooks/use-inline-blame";
9
10
 
10
11
  function getMonacoLanguage(filename: string): string {
11
12
  const ext = filename.split(".").pop()?.toLowerCase() ?? "";
@@ -49,6 +50,20 @@ export function DiffViewer({ metadata }: DiffViewerProps) {
49
50
  const containerRef = useRef<HTMLDivElement>(null);
50
51
  const diffEditorRef = useRef<import("monaco-editor").editor.IStandaloneDiffEditor | null>(null);
51
52
  const [editorReady, setEditorReady] = useState(false);
53
+ // The two panes, as state rather than refs: the blame hooks have to re-run
54
+ // once the editor exists. `focused` is which pane gets annotated — only one,
55
+ // because both panes carry a cursor position and annotating both would leave a
56
+ // stray line-1 annotation in the idle pane.
57
+ //
58
+ // It starts on the modified side rather than nothing: that is the side the
59
+ // normal editor annotates, so turning blame on shows something immediately
60
+ // instead of waiting for a click nobody knows to make.
61
+ const [panes, setPanes] = useState<{
62
+ monaco: typeof import("monaco-editor");
63
+ original: import("monaco-editor").editor.ICodeEditor;
64
+ modified: import("monaco-editor").editor.ICodeEditor;
65
+ } | null>(null);
66
+ const [focused, setFocused] = useState<"original" | "modified">("modified");
52
67
  const [containerHeight, setContainerHeight] = useState<number | undefined>();
53
68
 
54
69
  useEffect(() => {
@@ -134,6 +149,42 @@ export function DiffViewer({ metadata }: DiffViewerProps) {
134
149
  return langFile ? getMonacoLanguage(langFile) : "plaintext";
135
150
  }, [filePath, file1, file2]);
136
151
 
152
+ const inlineBlame = useSettingsStore((s) => s.inlineBlame);
153
+ const toggleInlineBlame = useSettingsStore((s) => s.toggleInlineBlame);
154
+
155
+ /**
156
+ * Blame is only honest on the full-file path.
157
+ *
158
+ * `file-full-diff` hands back both sides as complete files at real revisions,
159
+ * so a line number here is the line number git blamed. The other three paths
160
+ * cannot be blamed: inline content and a two-file compare are not a tracked
161
+ * path at a revision, and `parseDiff` rebuilds the file from hunks alone, so
162
+ * its line 40 is not the file's line 40 — annotating it would confidently
163
+ * name the wrong commit.
164
+ */
165
+ const canBlame = Boolean(projectName && filePath && fullFileDiff);
166
+
167
+ // The left pane is the file at `ref1` (the route defaults to HEAD); the right
168
+ // is `ref2`, or the working tree when there is none.
169
+ useInlineBlame({
170
+ editor: panes?.original ?? null,
171
+ monaco: panes?.monaco ?? null,
172
+ projectName,
173
+ filePath: canBlame ? filePath : undefined,
174
+ rev: ref1 || "HEAD",
175
+ enabled: inlineBlame && canBlame && focused === "original",
176
+ readOnly: true,
177
+ });
178
+ useInlineBlame({
179
+ editor: panes?.modified ?? null,
180
+ monaco: panes?.monaco ?? null,
181
+ projectName,
182
+ filePath: canBlame ? filePath : undefined,
183
+ rev: ref2,
184
+ enabled: inlineBlame && canBlame && focused === "modified",
185
+ readOnly: true,
186
+ });
187
+
137
188
  // Force inline on mobile (<768px) since side-by-side is too narrow
138
189
  const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
139
190
  const renderSideBySide = !isMobile;
@@ -190,13 +241,26 @@ export function DiffViewer({ metadata }: DiffViewerProps) {
190
241
  return (
191
242
  <div className="flex flex-col h-full">
192
243
  {/* Toolbar */}
193
- {!isMobile && (
244
+ {(!isMobile || canBlame) && (
194
245
  <div className="flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0">
195
- <button type="button" onClick={toggleWordWrap} title="Toggle word wrap"
196
- className={`p-1 rounded hover:bg-muted transition-colors ${wordWrap ? "bg-muted text-foreground" : ""}`}
197
- >
198
- <WrapText className="size-3.5" />
199
- </button>
246
+ {canBlame && (
247
+ <button type="button" onClick={toggleInlineBlame}
248
+ title="Inline blame (Alt+B) — who last touched the cursor's line. Click a pane to annotate that side."
249
+ className={`flex items-center justify-center rounded hover:bg-muted active:scale-95 transition-colors ${
250
+ isMobile ? "size-11" : "p-1"
251
+ } ${inlineBlame ? "bg-muted text-foreground" : ""}`}
252
+ >
253
+ <UserRound className="size-3.5" />
254
+ </button>
255
+ )}
256
+ {/* Word wrap is forced on below `md`, so its toggle would be a lie. */}
257
+ {!isMobile && (
258
+ <button type="button" onClick={toggleWordWrap} title="Toggle word wrap"
259
+ className={`p-1 rounded hover:bg-muted transition-colors ${wordWrap ? "bg-muted text-foreground" : ""}`}
260
+ >
261
+ <WrapText className="size-3.5" />
262
+ </button>
263
+ )}
200
264
  </div>
201
265
  )}
202
266
  {/* Monaco DiffEditor */}
@@ -208,9 +272,20 @@ export function DiffViewer({ metadata }: DiffViewerProps) {
208
272
  original={original}
209
273
  modified={modified}
210
274
  theme={monacoTheme}
211
- onMount={(editor) => {
275
+ onMount={(editor, monaco) => {
212
276
  diffEditorRef.current = editor;
213
277
  setEditorReady(true);
278
+ const originalPane = editor.getOriginalEditor();
279
+ const modifiedPane = editor.getModifiedEditor();
280
+ setPanes({ monaco, original: originalPane, modified: modifiedPane });
281
+ originalPane.onDidFocusEditorText(() => setFocused("original"));
282
+ modifiedPane.onDidFocusEditorText(() => setFocused("modified"));
283
+ // Same shortcut as the normal editor. It goes on the diff editor,
284
+ // not the panes: only IStandaloneDiffEditor has addCommand.
285
+ editor.addCommand(
286
+ monaco.KeyMod.Alt | monaco.KeyCode.KeyB,
287
+ () => useSettingsStore.getState().toggleInlineBlame(),
288
+ );
214
289
  }}
215
290
  options={{
216
291
  fontSize: isMobile ? 11 : 13,
@@ -1,4 +1,4 @@
1
- import { Code, Eye, WrapText, Table, Download, RefreshCw } from "lucide-react";
1
+ import { Code, Eye, WrapText, Table, Download, RefreshCw, UserRound } from "lucide-react";
2
2
  import { downloadFile } from "@/lib/file-download";
3
3
 
4
4
  interface EditorToolbarProps {
@@ -9,6 +9,11 @@ interface EditorToolbarProps {
9
9
  onCsvModeChange?: (mode: "table" | "raw") => void;
10
10
  wordWrap: boolean;
11
11
  onToggleWordWrap: () => void;
12
+ /** Omitted for buffers git cannot blame (untitled, inline preview). */
13
+ inlineBlame?: boolean;
14
+ onToggleInlineBlame?: () => void;
15
+ /** Blame is on but hidden because the buffer has unsaved edits. */
16
+ blameStale?: boolean;
12
17
  onRefresh?: () => void;
13
18
  refreshing?: boolean;
14
19
  filePath?: string;
@@ -21,15 +26,18 @@ function ToolbarButton({
21
26
  onClick,
22
27
  icon: Icon,
23
28
  label,
29
+ title,
24
30
  }: {
25
31
  active: boolean;
26
32
  onClick: () => void;
27
33
  icon: React.ComponentType<{ className?: string }>;
28
34
  label: string;
35
+ title?: string;
29
36
  }) {
30
37
  return (
31
38
  <button
32
39
  type="button"
40
+ title={title}
33
41
  onClick={onClick}
34
42
  className={`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${
35
43
  active ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground"
@@ -49,6 +57,9 @@ export function EditorToolbar({
49
57
  onCsvModeChange,
50
58
  wordWrap,
51
59
  onToggleWordWrap,
60
+ inlineBlame,
61
+ onToggleInlineBlame,
62
+ blameStale,
52
63
  onRefresh,
53
64
  refreshing,
54
65
  filePath,
@@ -78,6 +89,19 @@ export function EditorToolbar({
78
89
  icon={WrapText}
79
90
  label="Wrap"
80
91
  />
92
+ {onToggleInlineBlame && (
93
+ <ToolbarButton
94
+ active={inlineBlame === true}
95
+ onClick={onToggleInlineBlame}
96
+ icon={UserRound}
97
+ label="Blame"
98
+ title={
99
+ inlineBlame && blameStale
100
+ ? "Blame is hidden until this file is saved — the line numbers have moved"
101
+ : "Inline blame (Alt+B)"
102
+ }
103
+ />
104
+ )}
81
105
  {onRefresh && (
82
106
  <button
83
107
  type="button"