@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
@@ -11,12 +11,19 @@
11
11
  * An expired account (past `expiresAt` AND no refresh token) is dimmed and loses every
12
12
  * control except delete — toggling or exporting a token the server can no longer renew only
13
13
  * produces confusing failures.
14
+ *
15
+ * The footer states what the user can act on and nothing else. It deliberately no longer
16
+ * counts down the access token: that number renews itself every few hours, no one can do
17
+ * anything about it, and it is what made two revoked accounts look healthy for six days
18
+ * while every turn on them failed. The sign-in deadline replaced it.
14
19
  */
15
20
 
16
- import { Download, Eye, Trash2 } from "lucide-react";
21
+ import { Download, Eye, KeyRound, RefreshCw, Trash2 } from "lucide-react";
17
22
  import { Switch } from "@/components/ui/switch";
18
23
  import type { AccountInfo, AccountUsageEntry, OAuthProfileData } from "../../../lib/api-settings";
19
24
  import { AccountBucketRow } from "./account-bucket-row";
25
+ import { AccountHint } from "./account-hint";
26
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
20
27
  import { AccountCardShell } from "./accounts-pane-header";
21
28
  import { formatExpiry, formatLastUpdated, tokenStatus } from "./account-usage-format";
22
29
 
@@ -31,6 +38,16 @@ export interface AccountCardProps {
31
38
  onViewProfile?: (profile: OAuthProfileData, accountId: string) => void;
32
39
  /** Brief highlight when this account's usage numbers just changed. */
33
40
  flash?: boolean;
41
+ /**
42
+ * Move the current chat session onto this account. Omitted in Settings, where a card
43
+ * manages an account rather than routing a conversation to it — leaving it out is what
44
+ * keeps that screen behaving exactly as before.
45
+ */
46
+ onSelect?: (id: string) => void;
47
+ /** Why this account cannot be selected. Present means the choice is refused, and says so. */
48
+ unselectableReason?: string | null;
49
+ /** A selection is in flight for this card. */
50
+ selecting?: boolean;
34
51
  /** `list` fills the width, `strip` is a fixed-width card in a sideways scroller, `grid`
35
52
  * fills a cell of the fullscreen grid. */
36
53
  layout?: "list" | "strip" | "grid";
@@ -43,6 +60,7 @@ const STRIP_WIDTH = { readOnly: "min-w-[220px]", withActions: "min-w-[300px]" }
43
60
 
44
61
  export function AccountCard({
45
62
  entry, isActive, accountInfo, onToggle, toggling, onDelete, onExport, onViewProfile, flash,
63
+ onSelect, unselectableReason, selecting,
46
64
  layout = "list",
47
65
  }: AccountCardProps) {
48
66
  const { usage } = entry;
@@ -53,7 +71,32 @@ export function AccountCard({
53
71
  && accountInfo.expiresAt < Math.floor(Date.now() / 1000)
54
72
  );
55
73
  const ts = tokenStatus(accountInfo);
74
+ // Distinct from isExpired: this account still holds a refresh token, it is just one the
75
+ // server will not honour. Dim it like an expired card, but keep every control — signing
76
+ // in again goes through the same add flow, and delete has to stay reachable.
77
+ const needsReauth = !!accountInfo?.reauthRequired;
78
+ // A sign-in dies on a fixed schedule regardless of use, so the warning has to lead the
79
+ // last stretch of it rather than appear once it is already too late.
80
+ const grantExpiresAtMs = accountInfo?.grantExpiresAt ? accountInfo.grantExpiresAt * 1000 : null;
81
+ const grantEndingSoon = !!grantExpiresAtMs && grantExpiresAtMs - Date.now() < 5 * 86_400_000;
82
+ // The header already badges Expired, Sign in again and API key, and "long-lived" only
83
+ // restates what the sign-in chip beside it implies. That leaves the two states nothing
84
+ // else announces: a temporary token with no way to renew, and one that has lapsed but
85
+ // still has a refresh token behind it.
86
+ const showTokenStatus = ts.label === "temp" || ts.label === "unknown"
87
+ || (ts.label === "expired" && !isExpired && !needsReauth);
88
+ const grantExpiresOn = grantExpiresAtMs
89
+ ? new Date(grantExpiresAtMs).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })
90
+ : null;
56
91
  const hasActions = Boolean(onToggle || onDelete || onExport || onViewProfile);
92
+ // Every state that already dims this card also refuses the chat to it. `needsReauth` is
93
+ // the one worth naming separately: the token is present and unexpired, so nothing else
94
+ // here looks wrong, but the server has rejected it and no turn can run on it.
95
+ const refusedReason = isExpired
96
+ ? "Token expired — sign in again"
97
+ : needsReauth
98
+ ? "Sign in again to use this account"
99
+ : unselectableReason ?? null;
57
100
 
58
101
  const layoutClass = layout === "list"
59
102
  ? ""
@@ -67,7 +110,7 @@ export function AccountCard({
67
110
  active={isActive}
68
111
  flash={flash}
69
112
  dense={layout !== "list"}
70
- className={[layoutClass, isExpired ? "opacity-50" : ""].filter(Boolean).join(" ") || undefined}
113
+ className={[layoutClass, isExpired || needsReauth ? "opacity-50" : ""].filter(Boolean).join(" ") || undefined}
71
114
  data-testid="account-card"
72
115
  data-account-id={entry.accountId}
73
116
  >
@@ -75,32 +118,90 @@ export function AccountCard({
75
118
  <span className="text-sm font-medium truncate flex-1 min-w-0">
76
119
  {entry.accountLabel ?? entry.accountId.slice(0, 8)}
77
120
  </span>
78
- {isActive && <span className="text-[10px] text-primary shrink-0 font-medium">Active</span>}
79
- {isExpired && <span className="text-[10px] text-error shrink-0 font-medium">Expired</span>}
121
+ {isActive && (
122
+ <AccountHint className="text-[10px] text-primary shrink-0 font-medium" hint="The next turn will run on this account.">
123
+ Active
124
+ </AccountHint>
125
+ )}
126
+ {/* Occupies the slot the "Active" badge would, so a card is the same height whether
127
+ it is the one serving or one you could switch to. A row of its own below cost
128
+ every card that height, including the ones whose button did nothing. */}
129
+ {onSelect && !isActive && (
130
+ // Shown even when the account cannot serve, and clicking it then reports why.
131
+ // Hiding it instead left a blank where the other cards have a control, which reads
132
+ // as a missing feature rather than as a refusal with a reason behind it.
133
+ <Tooltip>
134
+ <TooltipTrigger asChild>
135
+ <button
136
+ type="button"
137
+ onClick={() => onSelect(entry.accountId)}
138
+ disabled={selecting}
139
+ className={[
140
+ "shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors cursor-pointer disabled:cursor-wait",
141
+ refusedReason
142
+ ? "text-text-subtle hover:text-error hover:bg-error/10"
143
+ : "text-text-secondary hover:text-foreground hover:bg-surface-elevated",
144
+ ].join(" ")}
145
+ >
146
+ {selecting ? "Switching…" : "Use"}
147
+ </button>
148
+ </TooltipTrigger>
149
+ <TooltipContent side="top">{refusedReason ?? "Use this account for this chat"}</TooltipContent>
150
+ </Tooltip>
151
+ )}
152
+ {isExpired && (
153
+ <AccountHint
154
+ className="text-[10px] text-error shrink-0 font-medium"
155
+ hint="This token has expired and carries no refresh token, so nothing can renew it. Add the account again."
156
+ >
157
+ Expired
158
+ </AccountHint>
159
+ )}
160
+ {needsReauth && !isExpired && (
161
+ <AccountHint
162
+ className="text-[10px] text-error shrink-0 font-medium"
163
+ hint="Anthropic rejected this account's refresh token, so no turn can run on it. Signing in again is the only thing that restores it."
164
+ >
165
+ Sign in again
166
+ </AccountHint>
167
+ )}
80
168
  {!entry.isOAuth && !isExpired && (
81
- <span className="text-[10px] text-text-subtle shrink-0">API key</span>
169
+ <AccountHint
170
+ className="text-[10px] text-text-subtle shrink-0"
171
+ hint="A static API key rather than a sign-in: nothing to expire and nothing to renew."
172
+ >
173
+ API key
174
+ </AccountHint>
82
175
  )}
83
176
 
84
177
  <div className="flex items-center gap-0.5 shrink-0">
85
178
  {!isExpired && onViewProfile && accountInfo?.profileData && (
86
- <button
87
- className="p-2 rounded cursor-pointer text-text-subtle hover:text-foreground hover:bg-surface-elevated transition-colors"
88
- onClick={() => onViewProfile(accountInfo.profileData!, entry.accountId)}
89
- title="View profile"
90
- aria-label="View profile"
91
- >
92
- <Eye className="size-4" />
93
- </button>
179
+ <Tooltip>
180
+ <TooltipTrigger asChild>
181
+ <button
182
+ className="p-2 rounded cursor-pointer text-text-subtle hover:text-foreground hover:bg-surface-elevated transition-colors"
183
+ onClick={() => onViewProfile(accountInfo.profileData!, entry.accountId)}
184
+ aria-label="View profile"
185
+ >
186
+ <Eye className="size-4" />
187
+ </button>
188
+ </TooltipTrigger>
189
+ <TooltipContent side="top">View profile</TooltipContent>
190
+ </Tooltip>
94
191
  )}
95
192
  {!isExpired && onExport && entry.isOAuth && (
96
- <button
97
- className="p-2 rounded cursor-pointer text-text-subtle hover:text-primary hover:bg-surface-elevated transition-colors"
98
- onClick={() => onExport(entry.accountId)}
99
- title="Export this account"
100
- aria-label="Export this account"
101
- >
102
- <Download className="size-4" />
103
- </button>
193
+ <Tooltip>
194
+ <TooltipTrigger asChild>
195
+ <button
196
+ className="p-2 rounded cursor-pointer text-text-subtle hover:text-primary hover:bg-surface-elevated transition-colors"
197
+ onClick={() => onExport(entry.accountId)}
198
+ aria-label="Export this account"
199
+ >
200
+ <Download className="size-4" />
201
+ </button>
202
+ </TooltipTrigger>
203
+ <TooltipContent side="top">Export this account</TooltipContent>
204
+ </Tooltip>
104
205
  )}
105
206
  {!isExpired && onToggle && (
106
207
  <Switch
@@ -112,14 +213,18 @@ export function AccountCard({
112
213
  />
113
214
  )}
114
215
  {onDelete && (
115
- <button
116
- className="p-2 rounded cursor-pointer text-text-subtle hover:text-error hover:bg-surface-elevated transition-colors"
117
- onClick={() => onDelete(entry.accountId, entry.accountLabel ?? entry.accountId.slice(0, 8))}
118
- title="Remove account"
119
- aria-label="Remove account"
120
- >
121
- <Trash2 className="size-4" />
122
- </button>
216
+ <Tooltip>
217
+ <TooltipTrigger asChild>
218
+ <button
219
+ className="p-2 rounded cursor-pointer text-text-subtle hover:text-error hover:bg-surface-elevated transition-colors"
220
+ onClick={() => onDelete(entry.accountId, entry.accountLabel ?? entry.accountId.slice(0, 8))}
221
+ aria-label="Remove account"
222
+ >
223
+ <Trash2 className="size-4" />
224
+ </button>
225
+ </TooltipTrigger>
226
+ <TooltipContent side="top">Remove account</TooltipContent>
227
+ </Tooltip>
123
228
  )}
124
229
  </div>
125
230
  </div>
@@ -139,13 +244,28 @@ export function AccountCard({
139
244
 
140
245
  <div className="flex items-center gap-2 text-[10px] text-text-subtle flex-wrap">
141
246
  {usage.lastFetchedAt && (
142
- <span title="Last usage data update">↻ {formatLastUpdated(new Date(usage.lastFetchedAt).getTime())}</span>
247
+ <AccountHint
248
+ className="inline-flex items-center gap-1"
249
+ hint={`Usage last read from Anthropic on ${new Date(usage.lastFetchedAt).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })}. The bars above are only as current as this.`}
250
+ >
251
+ <RefreshCw className="size-3 shrink-0" aria-hidden />
252
+ {formatLastUpdated(new Date(usage.lastFetchedAt).getTime())}
253
+ </AccountHint>
254
+ )}
255
+ {grantExpiresAtMs && !needsReauth && (
256
+ <AccountHint
257
+ className={["inline-flex items-center gap-1", grantEndingSoon ? "text-warning" : ""].filter(Boolean).join(" ")}
258
+ hint={`Sign-in stops working on ${grantExpiresOn}. Anthropic ends every sign-in on a fixed schedule from the day it was made, whether or not the account gets used — refreshing does not extend it. After that the account fails until you sign in again.`}
259
+ >
260
+ <KeyRound className="size-3 shrink-0" aria-hidden />
261
+ sign-in {formatExpiry(grantExpiresAtMs)}
262
+ </AccountHint>
143
263
  )}
144
- {accountInfo?.expiresAt && accountInfo.expiresAt * 1000 > Date.now() && (
145
- <span title="Token expires in">⏱ {formatExpiry(accountInfo.expiresAt * 1000)}</span>
264
+ {showTokenStatus && (
265
+ <AccountHint className={ts.color} hint={ts.tip}>© {ts.label}</AccountHint>
146
266
  )}
147
- <span className={ts.color} title={ts.tip}>© {ts.label}</span>
148
267
  </div>
268
+
149
269
  </AccountCardShell>
150
270
  );
151
271
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A card label that carries an explanation.
3
+ *
4
+ * The trigger is a button rather than a span on purpose. Radix opens a tooltip on focus as
5
+ * well as hover, so tapping the chip reaches the explanation on touch, where hover does not
6
+ * exist at all — `docs/design-guidelines.md` rules out hiding anything behind hover alone.
7
+ * The visible label still carries the information; this only makes the reasoning behind it
8
+ * reachable on every device rather than on desktop only.
9
+ */
10
+
11
+ import type { ReactNode } from "react";
12
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
13
+
14
+ export interface AccountHintProps {
15
+ hint: ReactNode;
16
+ className?: string;
17
+ children: ReactNode;
18
+ }
19
+
20
+ export function AccountHint({ hint, className, children }: AccountHintProps) {
21
+ return (
22
+ <Tooltip>
23
+ <TooltipTrigger asChild>
24
+ <button type="button" className={["cursor-help text-left", className].filter(Boolean).join(" ")}>
25
+ {children}
26
+ </button>
27
+ </TooltipTrigger>
28
+ <TooltipContent side="top" className="max-w-[260px] text-xs leading-relaxed">
29
+ {hint}
30
+ </TooltipContent>
31
+ </Tooltip>
32
+ );
33
+ }
@@ -49,6 +49,24 @@ export function formatResetTime(bucket?: LimitBucket): string | null {
49
49
  }
50
50
 
51
51
  /** Time until a token expires. Takes milliseconds. */
52
+ /**
53
+ * The exact moment a bucket resets, for the tooltip behind the relative "↻ 2h 10m".
54
+ *
55
+ * The relative figure is the right thing on the card — it answers "can I keep working" at a
56
+ * glance — but it cannot answer "will this be back before my meeting", and it goes stale as
57
+ * soon as it is rendered. The absolute time answers both, so it belongs one hover away rather
58
+ * than competing for the same few pixels.
59
+ *
60
+ * Rendered in the viewer's own locale and zone: the server sends UTC, and a reset time shown
61
+ * in UTC is a small puzzle to solve at exactly the moment someone is in a hurry.
62
+ */
63
+ export function formatResetAt(resetsAt?: string | null): string | null {
64
+ if (!resetsAt) return null;
65
+ const ms = new Date(resetsAt).getTime();
66
+ if (!Number.isFinite(ms)) return null;
67
+ return new Date(ms).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
68
+ }
69
+
52
70
  export function formatExpiry(expiresAtMs: number): string {
53
71
  const diff = expiresAtMs - Date.now();
54
72
  if (diff <= 0) return "expired";
@@ -65,10 +83,18 @@ export function formatExpiry(expiresAtMs: number): string {
65
83
  *
66
84
  * An expired token with a refresh token is only a warning, not an error: the server renews
67
85
  * it on the next call. Without one it is genuinely dead, which is why the two cases differ.
86
+ *
87
+ * Unless the server has already refused to renew it. PPM kept a rejected account holding its
88
+ * refresh token — deliberately, so a re-import can recover it — and this function read that
89
+ * as "will auto-renew" for six days while every turn on the account failed. A rejection
90
+ * outranks both other cases.
68
91
  */
69
92
  export function tokenStatus(info?: AccountInfo): { label: string; tip: string; color: string } {
70
93
  if (!info) return { label: "unknown", tip: "No account info available", color: "text-text-subtle" };
71
94
  if (!info.expiresAt) return { label: "key", tip: "API key (no expiry)", color: "text-text-subtle" };
95
+ if (info.reauthRequired) {
96
+ return { label: "sign in again", tip: "Anthropic rejected this account's refresh token. Sign in again to restore it.", color: "text-error" };
97
+ }
72
98
  const expired = info.expiresAt * 1000 < Date.now(); // expiresAt is seconds
73
99
  if (expired && info.hasRefreshToken) return { label: "expired", tip: "Token expired but has refresh token — will auto-renew", color: "text-warning" };
74
100
  if (expired) return { label: "expired", tip: "Token expired, no refresh token", color: "text-error" };
@@ -9,6 +9,8 @@
9
9
  import { useState } from "react";
10
10
  import { Download, KeyRound, Loader2, Plus, Settings, Trash2, Upload } from "lucide-react";
11
11
  import { Button } from "@/components/ui/button";
12
+ import { Switch } from "@/components/ui/switch";
13
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
12
14
  import { AccountCardRow, AccountCardShell, AccountsPaneHeader, AccountsPaneMessage } from "./accounts-pane-header";
13
15
  import { CodexAddAccountDialog } from "./codex-add-account-dialog";
14
16
  import { CodexBackupDialog } from "./codex-backup-dialog";
@@ -85,15 +87,28 @@ export function CodexAccountsSection() {
85
87
  {a.type}
86
88
  </span>
87
89
  {a.planType && <span className="text-[10px] text-text-subtle shrink-0">{a.planType}</span>}
88
- <button
89
- type="button"
90
- onClick={() => void c.remove(a.id)}
91
- title="Remove account"
92
- aria-label="Remove account"
93
- className="p-2 rounded cursor-pointer text-text-subtle hover:text-error hover:bg-surface-elevated transition-colors shrink-0"
94
- >
95
- <Trash2 className="size-4" />
96
- </button>
90
+ {/* Same control the Claude card uses, in the same place, so the two
91
+ sub-tabs read as one screen rather than two designs. */}
92
+ <Switch
93
+ checked={a.status !== "disabled"}
94
+ onCheckedChange={() => void c.toggle(a.id, a.status ?? "active")}
95
+ disabled={c.toggling.has(a.id)}
96
+ aria-label={a.status === "disabled" ? "Enable account" : "Disable account"}
97
+ className="cursor-pointer shrink-0"
98
+ />
99
+ <Tooltip>
100
+ <TooltipTrigger asChild>
101
+ <button
102
+ type="button"
103
+ onClick={() => void c.remove(a.id)}
104
+ aria-label="Remove account"
105
+ className="p-2 rounded cursor-pointer text-text-subtle hover:text-error hover:bg-surface-elevated transition-colors shrink-0"
106
+ >
107
+ <Trash2 className="size-4" />
108
+ </button>
109
+ </TooltipTrigger>
110
+ <TooltipContent side="top">Remove account</TooltipContent>
111
+ </Tooltip>
97
112
  </div>
98
113
  <CodexUsageRows usage={u} />
99
114
  </AccountCardShell>
@@ -19,8 +19,8 @@ function toPct(v?: number): number | null { return v != null ? Math.round(v * 10
19
19
  export function CodexUsageRows({ usage }: { usage: Usage }) {
20
20
  return (
21
21
  <div className="space-y-2">
22
- <AccountUsageBar label="5-Hour Session" pct={toPct(usage.fiveHour)} reset={formatResetTime(usage.session)} />
23
- <AccountUsageBar label="Weekly" pct={toPct(usage.sevenDay)} reset={formatResetTime(usage.weekly)} />
22
+ <AccountUsageBar label="5-Hour Session" pct={toPct(usage.fiveHour)} reset={formatResetTime(usage.session)} resetsAt={usage.session?.resetsAt} />
23
+ <AccountUsageBar label="Weekly" pct={toPct(usage.sevenDay)} reset={formatResetTime(usage.weekly)} resetsAt={usage.weekly?.resetsAt} />
24
24
  </div>
25
25
  );
26
26
  }
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { useCallback, useEffect, useRef, useState } from "react";
10
10
  import {
11
- getAccounts, getActiveAccount, getAllAccountUsages,
11
+ getAccounts, getActiveAccount, getAllAccountUsages, patchAccount,
12
12
  type AccountInfo, type AccountUsageEntry,
13
13
  } from "../../../lib/api-settings";
14
14
 
@@ -22,6 +22,10 @@ export interface AccountsData {
22
22
  /** Accounts whose utilisation changed on the last refresh — briefly highlighted. */
23
23
  flashIds: Set<string>;
24
24
  reload: () => Promise<void>;
25
+ /** Switch an account on or off, reloading when it lands. Resolves to an error message, or null. */
26
+ toggle: (id: string, status: string) => Promise<string | null>;
27
+ /** Account whose switch is mid-flight, or null. */
28
+ togglingId: string | null;
25
29
  }
26
30
 
27
31
  /** Utilisation across all four buckets, as a comparable string. */
@@ -37,6 +41,7 @@ export function useAccountsData(enabled = true): AccountsData {
37
41
  const [initialLoading, setInitialLoading] = useState(true);
38
42
  const [refreshing, setRefreshing] = useState(false);
39
43
  const [flashIds, setFlashIds] = useState<Set<string>>(new Set());
44
+ const [togglingId, setTogglingId] = useState<string | null>(null);
40
45
  const prevUsages = useRef<AccountUsageEntry[]>([]);
41
46
  const flashTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
42
47
 
@@ -72,10 +77,35 @@ export function useAccountsData(enabled = true): AccountsData {
72
77
  setRefreshing(false);
73
78
  }, []);
74
79
 
80
+ /**
81
+ * Switch an account on or off.
82
+ *
83
+ * Lives here rather than in either pane so Settings and the chat toolbar drive one
84
+ * implementation. Two of them is how those screens drifted apart before, and the drift is
85
+ * invisible until someone notices the same switch behaving differently in two places.
86
+ *
87
+ * Returns the server's message instead of throwing: enabling a parked account makes the
88
+ * server prove its token first, which can take most of a minute and can come back 400 with
89
+ * a message worth reading — "sign in again" and "could not reach Anthropic" call for
90
+ * different actions, and swallowing the difference leaves the switch silently snapping back.
91
+ */
92
+ const toggle = useCallback(async (id: string, status: string): Promise<string | null> => {
93
+ setTogglingId(id);
94
+ let error: string | null = null;
95
+ try {
96
+ await patchAccount(id, { status: status === "disabled" ? "active" : "disabled" });
97
+ } catch (e) {
98
+ error = (e as Error).message;
99
+ }
100
+ setTogglingId(null);
101
+ await reload();
102
+ return error;
103
+ }, [reload]);
104
+
75
105
  useEffect(() => {
76
106
  if (enabled) void reload();
77
107
  return () => clearTimeout(flashTimer.current);
78
108
  }, [enabled, reload]);
79
109
 
80
- return { usages, accounts, activeAccountId, initialLoading, refreshing, flashIds, reload };
110
+ return { usages, accounts, activeAccountId, initialLoading, refreshing, flashIds, reload, toggle, togglingId };
81
111
  }
@@ -15,7 +15,7 @@ import type { LimitBucket } from "../../../../types/chat";
15
15
  import type { CodexStrategy } from "./codex-rotation-dialog";
16
16
 
17
17
  export type Strategy = CodexStrategy;
18
- export interface CodexAccount { id: string; label: string; type: string; planType?: string | null }
18
+ export interface CodexAccount { id: string; label: string; type: string; planType?: string | null; status?: "active" | "disabled" }
19
19
  /**
20
20
  * One account's quota. The two percentages are what the bars read; the buckets carry the
21
21
  * reset clock the server already sends, so the Codex card can show "resets in" the way the
@@ -41,6 +41,7 @@ export function useCodexAccounts(onDone: () => void) {
41
41
  const [strategy, setStrategy] = useState<Strategy>("round-robin");
42
42
  const [usages, setUsages] = useState<Record<string, Usage>>({});
43
43
  const [loading, setLoading] = useState(false);
44
+ const [toggling, setToggling] = useState<Set<string>>(new Set());
44
45
  const [apiKey, setApiKey] = useState("");
45
46
  const [label, setLabel] = useState("");
46
47
  const [adding, setAdding] = useState(false);
@@ -117,6 +118,26 @@ export function useCodexAccounts(onDone: () => void) {
117
118
  };
118
119
 
119
120
  const remove = async (id: string) => { await api.del(`/api/codex-accounts/${id}`); await load(); };
121
+
122
+ /**
123
+ * Switch an account on or off.
124
+ *
125
+ * Tracked per account rather than with one shared flag: the pane shows every account side
126
+ * by side, and a single flag would grey out all of them while one is changing.
127
+ */
128
+ const toggle = async (id: string, status: string) => {
129
+ const next = status === "disabled" ? "active" : "disabled";
130
+ setToggling((prev) => new Set(prev).add(id));
131
+ setErr(null);
132
+ try {
133
+ await api.patch(`/api/codex-accounts/${id}`, { status: next });
134
+ await load();
135
+ } catch (e) {
136
+ setErr((e as Error).message || "Could not change the account");
137
+ } finally {
138
+ setToggling((prev) => { const n = new Set(prev); n.delete(id); return n; });
139
+ }
140
+ };
120
141
  const changeStrategy = async (s: Strategy) => { setStrategy(s); try { await api.put("/api/codex-accounts/strategy", { strategy: s }); } catch { /* revert on reload */ } };
121
142
 
122
143
  const doExport = async () => {
@@ -157,6 +178,7 @@ export function useCodexAccounts(onDone: () => void) {
157
178
  accounts, strategy, usages, loading, apiKey, setApiKey, label, setLabel, adding,
158
179
  err, setErr, device, deviceWaiting, backupPassword, setBackupPassword,
159
180
  exporting, importing, msg, setMsg,
160
- load, addApiKey, startDevice, remove, changeStrategy, doExport, doImport,
181
+ toggling,
182
+ load, addApiKey, startDevice, remove, toggle, changeStrategy, doExport, doImport,
161
183
  };
162
184
  }
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef, type ReactNode } from "react";
1
+ import { useState, useEffect, useMemo, useRef, type ReactNode } from "react";
2
2
  import { useMdContext, FILE_EXT_RE, GLOB_CHARS_RE } from "./markdown-context";
3
3
  import { highlightToHtml, highlightSync, getActiveShikiTheme } from "@/theme/adapters/shiki-adapter";
4
4
  import { copyToClipboard } from "@/lib/clipboard";
@@ -80,11 +80,14 @@ export function MdPre({ children, node, ...rest }: any) {
80
80
  // Highlight once the block is done streaming; render raw code until then to
81
81
  // avoid re-highlighting on every token (and Shiki being async).
82
82
  const shikiHtml = useShikiHtml(text, lang, !isStreaming);
83
+ // Stable object: React 19 re-sets innerHTML whenever this prop's identity changes, which
84
+ // would rebuild the highlighted subtree on every re-render of the surrounding message.
85
+ const shikiProp = useMemo(() => (shikiHtml ? { __html: shikiHtml } : null), [shikiHtml]);
83
86
 
84
87
  return (
85
88
  <div className="relative group">
86
- {shikiHtml
87
- ? <div className="shiki-block" dangerouslySetInnerHTML={{ __html: shikiHtml }} />
89
+ {shikiProp
90
+ ? <div className="shiki-block" dangerouslySetInnerHTML={shikiProp} />
88
91
  : <pre {...rest}>{children}</pre>}
89
92
  {codeActions && (
90
93
  <div className="code-actions absolute top-1 right-1 flex gap-1">
@@ -151,6 +154,8 @@ export function MdCode({ className, children, node, ...rest }: any) {
151
154
  function MermaidDiagram({ source }: { source: string }) {
152
155
  const { openDiagramOverlay } = useMdContext();
153
156
  const [svg, setSvg] = useState<string | null>(null);
157
+ // Same identity rule as the Shiki block: a fresh `{ __html }` per render re-parses the SVG.
158
+ const svgProp = useMemo(() => (svg ? { __html: svg } : null), [svg]);
154
159
 
155
160
  useEffect(() => {
156
161
  let cancelled = false;
@@ -174,14 +179,14 @@ function MermaidDiagram({ source }: { source: string }) {
174
179
  };
175
180
  }, [source]);
176
181
 
177
- if (!svg) return <pre><code>{source}</code></pre>;
182
+ if (!svg || !svgProp) return <pre><code>{source}</code></pre>;
178
183
 
179
184
  return (
180
185
  <div
181
186
  className="mermaid-diagram group relative cursor-pointer rounded-lg border border-border bg-white dark:bg-panel-2 p-3 overflow-x-auto my-2"
182
187
  onClick={() => openDiagramOverlay(svg)}
183
188
  >
184
- <div dangerouslySetInnerHTML={{ __html: svg }} />
189
+ <div dangerouslySetInnerHTML={svgProp} />
185
190
  <div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 rounded bg-black/60 text-white text-xs can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity pointer-events-none">
186
191
  Click to expand
187
192
  </div>
@@ -66,6 +66,13 @@ interface UseChatReturn {
66
66
  compactStatus: "compacting" | null;
67
67
  statusMessage: string | null;
68
68
  sessionTitle: string | null;
69
+ /**
70
+ * Account the server last reported as serving this session, from the stream rather than
71
+ * the polled usage endpoint — so a forced account switch shows up immediately instead of
72
+ * up to two minutes later. Carries the id as well as the label because the panel marks the
73
+ * serving card by id.
74
+ */
75
+ liveAccount: { id: string; label: string | null } | null;
69
76
  /** Per-session model override (null = provider default) */
70
77
  model: string | null;
71
78
  /** Switch the per-session model (persists + recreates query on next message) */
@@ -166,6 +173,17 @@ export function useChat(
166
173
  const streamingEventsRef = useRef<ChatEvent[]>([]);
167
174
  const bashOutputRef = useRef<Map<string, BashPartialEntry>>(new Map());
168
175
  const streamingAccountRef = useRef<{ accountId: string; accountLabel: string } | null>(null);
176
+ /**
177
+ * Which account last reported serving this session, as state rather than a ref so the
178
+ * toolbar re-renders on it.
179
+ *
180
+ * Deliberately sticky across turns: it is cleared when the session changes, not when a
181
+ * turn ends. The account that just served is still the account bound to the session, and
182
+ * the only other source for that label is the usage endpoint on a two-minute poll — so
183
+ * dropping it at turn end would leave the chip showing a stale name for minutes after the
184
+ * server was forced onto a different account.
185
+ */
186
+ const [liveAccount, setLiveAccount] = useState<{ id: string; label: string | null } | null>(null);
169
187
  const phaseRef = useRef<SessionPhase>("idle");
170
188
  const pendingMessageRef = useRef<string | null>(null);
171
189
  const sendRef = useRef<(data: string) => void>(() => {});
@@ -402,6 +420,7 @@ export function useChat(
402
420
  switch (evType) {
403
421
  case "account_info": {
404
422
  streamingAccountRef.current = { accountId: ev.accountId, accountLabel: ev.accountLabel };
423
+ setLiveAccount({ id: ev.accountId, label: ev.accountLabel ?? null });
405
424
  setStatusMessage(null);
406
425
  break;
407
426
  }
@@ -410,6 +429,9 @@ export function useChat(
410
429
  // Update streaming account to the new one being tried
411
430
  if (ev.accountId && ev.accountLabel) {
412
431
  streamingAccountRef.current = { accountId: ev.accountId, accountLabel: ev.accountLabel };
432
+ // A retry means the server was forced off the account the toolbar is naming —
433
+ // rate limit, usage cap or auth. Say so now rather than at the next usage poll.
434
+ setLiveAccount({ id: ev.accountId, label: ev.accountLabel });
413
435
  }
414
436
  // Clear previous streaming events (error text from failed attempt)
415
437
  // and start fresh with only the retry notification
@@ -961,6 +983,9 @@ export function useChat(
961
983
  if (prevSessionIdRef.current && prevSessionIdRef.current !== sessionId) {
962
984
  pendingModelRef.current = null;
963
985
  thinkingRef.current = null;
986
+ // One session's account must not label another's. Only on a real session-to-session
987
+ // switch: the draft→real transition (null → id) keeps serving the same conversation.
988
+ setLiveAccount(null);
964
989
  }
965
990
  prevSessionIdRef.current = sessionId ?? null;
966
991
 
@@ -1359,6 +1384,8 @@ export function useChat(
1359
1384
  compactStatus,
1360
1385
  statusMessage,
1361
1386
  sessionTitle,
1387
+ /** Account the server last reported for this session — beats the polled usage label. */
1388
+ liveAccount,
1362
1389
  model,
1363
1390
  setModel,
1364
1391
  effort,