@hienlh/ppm 0.21.0 → 0.22.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 (620) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/CLAUDE.md +29 -4
  3. package/THIRD-PARTY-NOTICES.md +1 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +4 -4
  6. package/assets/skills/ppm/references/http-api.md +1 -1
  7. package/bun.lock +16 -1
  8. package/dist/web/assets/accounts-settings-section-D3BylchP.js +1 -0
  9. package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-BLPcnhTM.js} +1 -1
  10. package/dist/web/assets/ai-settings-section-BtR7rF-T.js +1 -0
  11. package/dist/web/assets/ai-settings-section-r9JctHNM.js +1 -0
  12. package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
  13. package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
  14. package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
  15. package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
  16. package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
  17. package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
  18. package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
  19. package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-CiMdk3sb.js} +1 -1
  20. package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
  21. package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
  22. package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
  23. package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
  24. package/dist/web/assets/branch-review-tab-B1pFjlKM.js +1 -0
  25. package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
  26. package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
  27. package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
  28. package/dist/web/assets/chat-tab-DZagX06m.js +15 -0
  29. package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
  30. package/dist/web/assets/code-editor-BJhOEsnZ.js +7 -0
  31. package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-CaCQJ4r_.js} +1 -1
  32. package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
  33. package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-B7u_p49h.js} +3 -3
  34. package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
  35. package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
  36. package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
  37. package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-CttjJU1e.js} +1 -1
  38. package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
  39. package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-Ca4HxIfx.js} +1 -1
  40. package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
  41. package/dist/web/assets/diff-viewer-9-XjLIwa.js +4 -0
  42. package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
  43. package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
  44. package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
  45. package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-D0DuEOs_.js} +1 -1
  46. package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
  47. package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
  48. package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
  49. package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
  50. package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
  51. package/dist/web/assets/explorer-body-VdCBqcZu.js +2 -0
  52. package/dist/web/assets/explorer-window-content-BIsbl7Th.js +1 -0
  53. package/dist/web/assets/extension-manager-section-HkY7geS5.js +1 -0
  54. package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-DZG8YHX9.js} +1 -1
  55. package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
  56. package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
  57. package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
  58. package/dist/web/assets/files-settings-section-qnRtkX05.js +1 -0
  59. package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
  60. package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
  61. package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
  62. package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
  63. package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-BAFSZmCE.js} +1 -1
  64. package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
  65. package/dist/web/assets/git-log-panel-2sGpfEla.js +1 -0
  66. package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
  67. package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
  68. package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
  69. package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-f0hyITiF.js} +7 -7
  70. package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
  71. package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
  72. package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
  73. package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
  74. package/dist/web/assets/group-chat-tab-Ck6sk7yY.js +1 -0
  75. package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
  76. package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
  77. package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
  78. package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
  79. package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
  80. package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
  81. package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
  82. package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
  83. package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
  84. package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
  85. package/dist/web/assets/index-BH99TIE0.css +1 -0
  86. package/dist/web/assets/index-CEZVxM4G.js +52 -0
  87. package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
  88. package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
  89. package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-CeLmhvu1.js} +1 -1
  90. package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
  91. package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
  92. package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
  93. package/dist/web/assets/keybindings-store-C_3ChOEf.js +1 -0
  94. package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-PVz6kuh2.js} +1 -1
  95. package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
  96. package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
  97. package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
  98. package/dist/web/assets/{markdown-renderer-CvL1pWeK.js → markdown-renderer-C140Ijk0.js} +3 -3
  99. package/dist/web/assets/markdown-renderer-Dz7-DYUg.js +1 -0
  100. package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
  101. package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
  102. package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
  103. package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
  104. package/dist/web/assets/mobile-explorer-sheet-CUG_owdf.js +1 -0
  105. package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
  106. package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
  107. package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
  108. package/dist/web/assets/notification-store-6vib1ISy.js +1 -0
  109. package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
  110. package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
  111. package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
  112. package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
  113. package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
  114. package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
  115. package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
  116. package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
  117. package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
  118. package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
  119. package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-Cji-Mil-.js} +1 -1
  120. package/dist/web/assets/problems-panel-a6_hrWvl.js +1 -0
  121. package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-CiIqlOIK.js} +1 -1
  122. package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
  123. package/dist/web/assets/project-store-B87hLRSk.js +1 -0
  124. package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-BjI4IFgp.js} +1 -1
  125. package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
  126. package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
  127. package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
  128. package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
  129. package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
  130. package/dist/web/assets/remote-desktop-clipboard-notice-eXYvwgx7.js +1 -0
  131. package/dist/web/assets/remote-desktop-mobile-sheet-Doh-fci-.js +2 -0
  132. package/dist/web/assets/remote-desktop-mobile-view-Dt8Akc3p.js +1 -0
  133. package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-t83fqbID.js} +1 -1
  134. package/dist/web/assets/remote-desktop-window-content-CguhZMmx.js +1 -0
  135. package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
  136. package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
  137. package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
  138. package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
  139. package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-BVkpW5ms.js} +1 -1
  140. package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
  141. package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
  142. package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
  143. package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
  144. package/dist/web/assets/settings-body-CoIxYAh6.js +2 -0
  145. package/dist/web/assets/settings-store-krC6bc79.js +2 -0
  146. package/dist/web/assets/settings-tab-7DKVUTbM.js +1 -0
  147. package/dist/web/assets/settings-window-content-3_39TeX-.js +1 -0
  148. package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
  149. package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
  150. package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
  151. package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
  152. package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-CQB7Yp7U.js} +1 -1
  153. package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-Bd7rjr1D.js} +1 -1
  154. package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
  155. package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
  156. package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
  157. package/dist/web/assets/system-monitor-body-DsRXeMyU.js +2 -0
  158. package/dist/web/assets/system-monitor-tab-DdSZgux3.js +1 -0
  159. package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-ClPo4-El.js} +1 -1
  160. package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-d2TAD4PU.js} +1 -1
  161. package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
  162. package/dist/web/assets/team-member-sheet-7d2Qp-ie.js +2 -0
  163. package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-Bctg2gmg.js} +1 -1
  164. package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
  165. package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-0HQic25V.js} +3 -3
  166. package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
  167. package/dist/web/assets/tool-cards-CmLLRACd.js +9 -0
  168. package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
  169. package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
  170. package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
  171. package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
  172. package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-sDMjcz1-.js} +3 -3
  173. package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CBgID-EU.js} +1 -1
  174. package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-gvhJ_jZh.js} +1 -1
  175. package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
  176. package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-C9wlRliB.js} +1 -1
  177. package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
  178. package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
  179. package/dist/web/assets/video-preview-CSDK5j6H.js +1 -0
  180. package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
  181. package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
  182. package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
  183. package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
  184. package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
  185. package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
  186. package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
  187. package/dist/web/index.html +27 -26
  188. package/dist/web/sw.js +1 -1
  189. package/docs/architecture/ai-chat-and-providers.md +9 -0
  190. package/docs/architecture/plugins-and-tracing.md +285 -0
  191. package/docs/lessons-learned.md +31 -0
  192. package/docs/system-architecture.md +1 -0
  193. package/package.json +2 -1
  194. package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
  195. package/packages/ext-git-graph/src/compare-view.ts +22 -3
  196. package/packages/ext-git-graph/src/extension.ts +6 -0
  197. package/packages/ext-git-graph/src/types.ts +19 -0
  198. package/packages/ext-git-graph/src/webview-html.test.ts +159 -1
  199. package/packages/ext-git-graph/src/webview-html.ts +344 -26
  200. package/scripts/gen-file-icons.ts +493 -242
  201. package/src/providers/claude-agent-sdk.ts +42 -1
  202. package/src/providers/codex-app-server/codex-history.ts +77 -23
  203. package/src/server/index.ts +42 -7
  204. package/src/server/routes/chat.ts +15 -11
  205. package/src/server/routes/git.ts +83 -3
  206. package/src/server/routes/remote-desktop.ts +7 -2
  207. package/src/server/ws/global.ts +1 -1
  208. package/src/server/ws/remote-desktop.ts +12 -5
  209. package/src/services/binary-content.ts +25 -0
  210. package/src/services/chat-search.service.ts +1 -1
  211. package/src/services/chat.service.ts +9 -0
  212. package/src/services/edge-forwarder.ts +191 -191
  213. package/src/services/event-loop-lag.ts +208 -0
  214. package/src/services/file-lines.ts +114 -0
  215. package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
  216. package/src/services/file-watcher/watch-tree.ts +131 -10
  217. package/src/services/file-watcher.service.ts +73 -23
  218. package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
  219. package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
  220. package/src/services/git-hunks/git-hunks.service.ts +26 -11
  221. package/src/services/git-hunks/unified-diff.ts +15 -1
  222. package/src/services/git.service.ts +74 -24
  223. package/src/services/jsonl-transcript-parser.ts +138 -6
  224. package/src/services/log-rotate.ts +142 -0
  225. package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
  226. package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
  227. package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
  228. package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
  229. package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
  230. package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
  231. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
  232. package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
  233. package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
  234. package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
  235. package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
  236. package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
  237. package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
  238. package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
  239. package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
  240. package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
  241. package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
  242. package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
  243. package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
  244. package/src/services/supervisor.ts +47 -4
  245. package/src/services/windows-process-tree.ts +351 -351
  246. package/src/shared/codex-daily-guard.ts +12 -5
  247. package/src/shared/remote-desktop-custom-quality.ts +117 -0
  248. package/src/shared/remote-desktop-quality.ts +127 -0
  249. package/src/types/chat.ts +4 -0
  250. package/src/types/git.ts +52 -0
  251. package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
  252. package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
  253. package/src/web/components/chat/attachment-chips.tsx +30 -6
  254. package/src/web/components/chat/chat-tab.tsx +12 -1
  255. package/src/web/components/chat/message-events.tsx +273 -0
  256. package/src/web/components/chat/message-list.tsx +107 -861
  257. package/src/web/components/chat/message-markdown.tsx +34 -0
  258. package/src/web/components/chat/message-media.tsx +174 -0
  259. package/src/web/components/chat/message-user-bubble.tsx +399 -0
  260. package/src/web/components/chat/provider-selector.tsx +30 -22
  261. package/src/web/components/chat/session-history-list.tsx +2 -2
  262. package/src/web/components/chat/session-list-panel.tsx +3 -2
  263. package/src/web/components/editor/binary-diff-view.tsx +212 -0
  264. package/src/web/components/editor/diff-viewer.tsx +76 -3
  265. package/src/web/components/explorer/file-tree.tsx +235 -134
  266. package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
  267. package/src/web/components/explorer/tree-node.tsx +69 -69
  268. package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
  269. package/src/web/components/extensions/extension-tree-view.tsx +4 -11
  270. package/src/web/components/git/git-status-panel.tsx +25 -0
  271. package/src/web/components/layout/command-palette.tsx +2 -18
  272. package/src/web/components/layout/draggable-tab.tsx +4 -2
  273. package/src/web/components/layout/nav-section-rail.tsx +12 -3
  274. package/src/web/components/layout/status-bar.tsx +10 -6
  275. package/src/web/components/layout/tab-content.tsx +5 -0
  276. package/src/web/components/layout/tab-pool.tsx +1 -0
  277. package/src/web/components/layout/upgrade-button.tsx +5 -1
  278. package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
  279. package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
  280. package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
  281. package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
  282. package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
  283. package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
  284. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
  285. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
  286. package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
  287. package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
  288. package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
  289. package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
  290. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
  291. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
  292. package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
  293. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
  294. package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
  295. package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
  296. package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
  297. package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
  298. package/src/web/components/settings/accounts/account-card.tsx +2 -2
  299. package/src/web/components/settings/accounts/codex-accounts-section.tsx +4 -4
  300. package/src/web/components/settings/extension-manager-section.tsx +8 -1
  301. package/src/web/components/settings/theme-picker.tsx +2 -1
  302. package/src/web/components/ui/tooltip.tsx +15 -2
  303. package/src/web/lib/branch-review-state.ts +175 -0
  304. package/src/web/lib/extension-icons.ts +70 -0
  305. package/src/web/lib/file-icon-name.ts +62 -0
  306. package/src/web/lib/file-icons.generated.ts +3287 -25
  307. package/src/web/lib/file-icons.tsx +71 -73
  308. package/src/web/lib/flatten-expansions.ts +18 -5
  309. package/src/web/lib/message-ordinals.ts +23 -0
  310. package/src/web/lib/provider-logos.tsx +143 -0
  311. package/src/web/lib/tab-type-icons.ts +25 -0
  312. package/src/web/stores/file-store.ts +73 -35
  313. package/src/web/stores/panel-utils.ts +5 -0
  314. package/src/web/stores/project-framework-store.ts +82 -0
  315. package/src/web/stores/project-store.ts +7 -1
  316. package/src/web/stores/settings-store.ts +128 -0
  317. package/src/web/stores/tab-store.ts +1 -0
  318. package/src/web/stores/window-panel-persistence.ts +1 -0
  319. package/src/web/styles/file-icons.generated.css +3282 -234
  320. package/src/web/styles/globals.css +26 -0
  321. package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
  322. package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
  323. package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
  324. package/dist/web/assets/api-client-BUCo1lms.js +0 -1
  325. package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
  326. package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
  327. package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
  328. package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
  329. package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
  330. package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
  331. package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
  332. package/dist/web/assets/extension-manager-section-BhwB6nqr.js +0 -1
  333. package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
  334. package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
  335. package/dist/web/assets/file-store-DMABxOkV.js +0 -1
  336. package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
  337. package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
  338. package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
  339. package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
  340. package/dist/web/assets/index-D7DbXD3G.css +0 -1
  341. package/dist/web/assets/index-ZtfxZOyb.js +0 -52
  342. package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
  343. package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
  344. package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
  345. package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
  346. package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
  347. package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
  348. package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
  349. package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
  350. package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
  351. package/dist/web/assets/project-store-BhMbVln3.js +0 -1
  352. package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
  353. package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
  354. package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
  355. package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
  356. package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
  357. package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
  358. package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
  359. package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
  360. package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
  361. package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
  362. package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
  363. package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
  364. package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
  365. package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
  366. package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
  367. package/dist/web/assets/ws-auth-DEAJiGCR.js +0 -1
  368. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  369. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  370. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  371. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  372. /package/dist/web/assets/{abap-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
  373. /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
  374. /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
  375. /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
  376. /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
  377. /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
  378. /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
  379. /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
  380. /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
  381. /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
  382. /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
  383. /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
  384. /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
  385. /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
  386. /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
  387. /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
  388. /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
  389. /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
  390. /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
  391. /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
  392. /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
  393. /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
  394. /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
  395. /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
  396. /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
  397. /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
  398. /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
  399. /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
  400. /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
  401. /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
  402. /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
  403. /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
  404. /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
  405. /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
  406. /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
  407. /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
  408. /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
  409. /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
  410. /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
  411. /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
  412. /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
  413. /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
  414. /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
  415. /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
  416. /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
  417. /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
  418. /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
  419. /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
  420. /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
  421. /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
  422. /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
  423. /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
  424. /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
  425. /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
  426. /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
  427. /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
  428. /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
  429. /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
  430. /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
  431. /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
  432. /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
  433. /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
  434. /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
  435. /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
  436. /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
  437. /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
  438. /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
  439. /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
  440. /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
  441. /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
  442. /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
  443. /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
  444. /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
  445. /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
  446. /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
  447. /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
  448. /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
  449. /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
  450. /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
  451. /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
  452. /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
  453. /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
  454. /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
  455. /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
  456. /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
  457. /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
  458. /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
  459. /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
  460. /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
  461. /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
  462. /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
  463. /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
  464. /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
  465. /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
  466. /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
  467. /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
  468. /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
  469. /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
  470. /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
  471. /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
  472. /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
  473. /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
  474. /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
  475. /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
  476. /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
  477. /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
  478. /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
  479. /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
  480. /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
  481. /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
  482. /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
  483. /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
  484. /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
  485. /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
  486. /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
  487. /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
  488. /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
  489. /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
  490. /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
  491. /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
  492. /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
  493. /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
  494. /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
  495. /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
  496. /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
  497. /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
  498. /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
  499. /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
  500. /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
  501. /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
  502. /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
  503. /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
  504. /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
  505. /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
  506. /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
  507. /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
  508. /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
  509. /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
  510. /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
  511. /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
  512. /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
  513. /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
  514. /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
  515. /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
  516. /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
  517. /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
  518. /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
  519. /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
  520. /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
  521. /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
  522. /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
  523. /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
  524. /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
  525. /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
  526. /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
  527. /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
  528. /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
  529. /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
  530. /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
  531. /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
  532. /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
  533. /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
  534. /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
  535. /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
  536. /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
  537. /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
  538. /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
  539. /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
  540. /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
  541. /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
  542. /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
  543. /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
  544. /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
  545. /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
  546. /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
  547. /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
  548. /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
  549. /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
  550. /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
  551. /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
  552. /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
  553. /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
  554. /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
  555. /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
  556. /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
  557. /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
  558. /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
  559. /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
  560. /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
  561. /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
  562. /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
  563. /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
  564. /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
  565. /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
  566. /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
  567. /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
  568. /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
  569. /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
  570. /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
  571. /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
  572. /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
  573. /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
  574. /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
  575. /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
  576. /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
  577. /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
  578. /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
  579. /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
  580. /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
  581. /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
  582. /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
  583. /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
  584. /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
  585. /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
  586. /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
  587. /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
  588. /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
  589. /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
  590. /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
  591. /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
  592. /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
  593. /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
  594. /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
  595. /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
  596. /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
  597. /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
  598. /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
  599. /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
  600. /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
  601. /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
  602. /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
  603. /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
  604. /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
  605. /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
  606. /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
  607. /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
  608. /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
  609. /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
  610. /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
  611. /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
  612. /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
  613. /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
  614. /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
  615. /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
  616. /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
  617. /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
  618. /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
  619. /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
  620. /package/dist/web/assets/{zig-B2htezJ_.js → zig-CbaUfBQe.js} +0 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Branch Review — a whole branch's changes as one reviewable list.
3
+ *
4
+ * The surface this exists for: a feature branch with a dozen commits, where
5
+ * opening each commit in turn shows the same file several times in states that
6
+ * were never meant to be read on their own. This shows the end state of every
7
+ * file against where the branch left the base, once.
8
+ *
9
+ * Two things are load-bearing and easy to get wrong:
10
+ *
11
+ * - Every file is opened at `mergeBase`, the commit the server measured the
12
+ * list against — never at `base` itself. The base branch keeps moving while a
13
+ * branch is in review, and diffing against its tip renders the base's own
14
+ * later commits as deletions inside the branch's review.
15
+ * - Paths from the git API are **repository**-relative, and `DiffViewer` takes
16
+ * **project**-relative ones (it rebases them back itself). A project whose
17
+ * root is a container of checkouts is where those differ, and handing the
18
+ * viewer the wrong one shows an empty diff for a file that plainly exists.
19
+ */
20
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
21
+ import { api } from "@/lib/api-client";
22
+ import { useGitRepo } from "@/hooks/use-git-repo";
23
+ import { useIsMobile } from "@/hooks/use-is-mobile";
24
+ import { DiffViewer } from "@/components/editor/diff-viewer";
25
+ import { Button } from "@/components/ui/button";
26
+ import { ScrollArea } from "@/components/ui/scroll-area";
27
+ import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
28
+ import { GitRepoBar, GitRepoChoice, GitNoRepo } from "@/components/git/git-repo-picker";
29
+ import { FileIcon } from "@/lib/file-icons";
30
+ import { buildTree, compactTree } from "@/lib/git-file-tree";
31
+ import { StartEllipsis, TreeRow } from "./branch-review-tree-row";
32
+ import {
33
+ Check, ChevronDown, ChevronRight, FileText, Loader2, RefreshCw, ListChecks, ArrowRight,
34
+ } from "@/lib/icons";
35
+ import {
36
+ firstReviewable, isReviewed, loadReviewed, nextUnreviewed, pruneReviewed, reviewKey, reviewedCount,
37
+ saveReviewed, setAllReviewed, toggleReviewed, type ReviewState,
38
+ } from "@/lib/branch-review-state";
39
+ import type { BranchDiff, BranchDiffFile, GitBranch } from "../../../types/git";
40
+
41
+ /** The base a review defaults to: the branch a feature is normally cut from. */
42
+ function defaultBase(branches: GitBranch[], current: string | undefined): string {
43
+ const local = branches.filter((b) => !b.remote && b.name !== current);
44
+ return (
45
+ local.find((b) => b.name === "main")?.name ??
46
+ local.find((b) => b.name === "master")?.name ??
47
+ local[0]?.name ??
48
+ ""
49
+ );
50
+ }
51
+
52
+ interface BranchReviewTabProps {
53
+ metadata?: Record<string, unknown>;
54
+ }
55
+
56
+ export function BranchReviewTab({ metadata }: BranchReviewTabProps) {
57
+ const projectName = metadata?.projectName as string | undefined;
58
+ const gitRepo = useGitRepo(projectName);
59
+ const isMobile = useIsMobile();
60
+
61
+ const [branches, setBranches] = useState<GitBranch[]>([]);
62
+ const [base, setBase] = useState("");
63
+ const [head, setHead] = useState("");
64
+ const [diff, setDiff] = useState<BranchDiff | null>(null);
65
+ const [selectedPath, setSelectedPath] = useState<string | null>(null);
66
+ const [reviewed, setReviewed] = useState<ReviewState>({});
67
+ const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
68
+ const [loading, setLoading] = useState(false);
69
+ const [error, setError] = useState<string | null>(null);
70
+ const [listOpen, setListOpen] = useState(false);
71
+
72
+ // Branch list → the two pickers, and the defaults a review opens on.
73
+ useEffect(() => {
74
+ if (!projectName || !gitRepo.repo) return;
75
+ let cancelled = false;
76
+ api
77
+ .get<GitBranch[]>(gitRepo.gitUrl("/branches"))
78
+ .then((list) => {
79
+ if (cancelled) return;
80
+ setBranches(list);
81
+ const current = list.find((b) => b.current)?.name;
82
+ setHead((h) => h || current || "");
83
+ setBase((b) => b || defaultBase(list, current));
84
+ })
85
+ .catch((e) => !cancelled && setError(e instanceof Error ? e.message : "Could not list branches"));
86
+ return () => { cancelled = true; };
87
+ }, [projectName, gitRepo]);
88
+
89
+ /**
90
+ * The comparison currently allowed to write. Change a picker twice on a large
91
+ * repository and two requests are in flight: the slower one answering last
92
+ * would put the previous pair's file list under the new pair's pickers, and
93
+ * `stateKey` below is derived from `base`/`head` — so review ticks would be
94
+ * read and written against a key that does not describe the list on screen.
95
+ * Its stale `setLoading(false)` also clears the spinner while the real request
96
+ * is still running.
97
+ *
98
+ * A ref rather than a flag in the effect's cleanup, because the Reload button
99
+ * calls `loadDiff` outside the effect and races exactly the same way.
100
+ */
101
+ const diffRequest = useRef<AbortController | null>(null);
102
+
103
+ const loadDiff = useCallback(async () => {
104
+ if (!projectName || !gitRepo.repo || !base || !head) return;
105
+ diffRequest.current?.abort();
106
+ const request = new AbortController();
107
+ diffRequest.current = request;
108
+ setLoading(true);
109
+ setError(null);
110
+ try {
111
+ const params = new URLSearchParams({ base, head });
112
+ const result = await api.get<BranchDiff>(gitRepo.gitUrl(`/branch-diff?${params}`), {
113
+ signal: request.signal,
114
+ });
115
+ if (request.signal.aborted) return;
116
+ setDiff(result);
117
+ setSelectedPath((current) =>
118
+ current && result.files.some((f) => f.path === current)
119
+ ? current
120
+ : firstReviewable(result.files)?.path ?? null,
121
+ );
122
+ } catch (e) {
123
+ if (request.signal.aborted) return;
124
+ setDiff(null);
125
+ setError(e instanceof Error ? e.message : "Could not compare these branches");
126
+ } finally {
127
+ if (!request.signal.aborted) setLoading(false);
128
+ }
129
+ }, [projectName, gitRepo, base, head]);
130
+
131
+ useEffect(() => {
132
+ void loadDiff();
133
+ return () => diffRequest.current?.abort();
134
+ }, [loadDiff]);
135
+
136
+ // Review progress is keyed by ref names, so it survives new commits; which
137
+ // files are *still* reviewed is decided per file by the blob id.
138
+ const stateKey = projectName && base && head ? reviewKey(projectName, base, head) : null;
139
+
140
+ // Which directories are folded describes one file tree. Carrying it into the
141
+ // next comparison collapses paths that are not the same paths, so a review
142
+ // opens with folders shut for reasons belonging to the review before it.
143
+ useEffect(() => { setCollapsed(new Set()); }, [stateKey]);
144
+ useEffect(() => {
145
+ setReviewed(stateKey ? loadReviewed(stateKey) : {});
146
+ }, [stateKey]);
147
+
148
+ /**
149
+ * The diff for the pair that is selected *now*, or null while one is being fetched.
150
+ *
151
+ * `stateKey` changes with the picker; `diff` only changes when a fetch lands. For the turns
152
+ * in between they describe two different comparisons, and everything downstream would be
153
+ * reading one pair's file list under the other pair's key — the prune below would keep only
154
+ * the paths the two happen to share and write that back, silently deleting review progress
155
+ * that had been recorded against the pair just switched to.
156
+ */
157
+ const currentDiff = diff && diff.base === base && diff.head === head ? diff : null;
158
+
159
+ // Prune once the file list arrives: paths a rebase removed would otherwise
160
+ // stay in localStorage forever.
161
+ useEffect(() => {
162
+ if (!stateKey || !currentDiff) return;
163
+ setReviewed((current) => {
164
+ const pruned = pruneReviewed(current, currentDiff.files);
165
+ if (Object.keys(pruned).length !== Object.keys(current).length) saveReviewed(stateKey, pruned);
166
+ return pruned;
167
+ });
168
+ }, [stateKey, currentDiff]);
169
+
170
+ const commitReviewed = useCallback((next: ReviewState) => {
171
+ setReviewed(next);
172
+ if (stateKey) saveReviewed(stateKey, next);
173
+ }, [stateKey]);
174
+
175
+ // The stale list is not rendered either: a tick on it would write the old pair's path and
176
+ // blob id under the new pair's key, which is the same bug seen from the other end.
177
+ const files = currentDiff?.files ?? [];
178
+ const byPath = useMemo(() => new Map(files.map((f) => [f.path, f])), [files]);
179
+ const doneCount = useMemo(() => reviewedCount(reviewed, files), [reviewed, files]);
180
+
181
+ // `buildTree` speaks the Source Control panel's shape; only path and status
182
+ // are read from it, and `T` (a mode-only change) has no equivalent there.
183
+ const tree = useMemo(
184
+ () => compactTree(buildTree(files.map((f) => ({
185
+ path: f.path,
186
+ status: f.status === "T" ? "M" : f.status,
187
+ })))),
188
+ [files],
189
+ );
190
+
191
+ const selected = selectedPath ? byPath.get(selectedPath) ?? null : null;
192
+
193
+ const goToNextUnreviewed = useCallback(() => {
194
+ const next = nextUnreviewed(reviewed, files, selectedPath);
195
+ if (next) {
196
+ setSelectedPath(next.path);
197
+ setListOpen(false);
198
+ }
199
+ }, [reviewed, files, selectedPath]);
200
+
201
+ if (gitRepo.noRepo) return <GitNoRepo onReload={gitRepo.reload} />;
202
+ if (gitRepo.needsPick) return <GitRepoChoice repos={gitRepo.repos} onChoose={gitRepo.choose} />;
203
+
204
+ const fileList = (
205
+ <ScrollArea className="h-full">
206
+ <div className="py-1">
207
+ {files.length === 0 && !loading && (
208
+ <div className="px-3 py-6 text-center text-xs text-text-3">
209
+ {error ? "—" : "These branches have identical trees."}
210
+ </div>
211
+ )}
212
+ {tree.map((node) => (
213
+ <TreeRow
214
+ key={node.fullPath}
215
+ node={node}
216
+ depth={0}
217
+ byPath={byPath}
218
+ reviewed={reviewed}
219
+ selectedPath={selectedPath}
220
+ collapsed={collapsed}
221
+ onToggleCollapse={(path) => setCollapsed((c) => {
222
+ const next = new Set(c);
223
+ if (next.has(path)) next.delete(path); else next.add(path);
224
+ return next;
225
+ })}
226
+ onSelect={(path) => { setSelectedPath(path); setListOpen(false); }}
227
+ onToggleReviewed={(file) => commitReviewed(toggleReviewed(reviewed, file))}
228
+ />
229
+ ))}
230
+ {!!currentDiff?.omitted && (
231
+ // Said rather than silently dropped: a list that simply ends looks
232
+ // like a branch with fewer changes than it has, and the count is the
233
+ // only clue that reviewing this one file-by-file is the wrong tool.
234
+ <div className="px-3 py-3 text-center text-xs text-text-3">
235
+ {currentDiff.omitted.toLocaleString()} more {currentDiff.omitted === 1 ? "file" : "files"} not listed.
236
+ </div>
237
+ )}
238
+ </div>
239
+ </ScrollArea>
240
+ );
241
+
242
+ return (
243
+ <div className="flex flex-col h-full overflow-hidden bg-bg">
244
+ {gitRepo.isNested && gitRepo.repo && (
245
+ <GitRepoBar repo={gitRepo.repo} repos={gitRepo.repos} onChoose={gitRepo.choose} />
246
+ )}
247
+
248
+ {/*
249
+ Native selects rather than the shadcn one: a repository can have
250
+ hundreds of branches, and the OS picker is both scrollable and a far
251
+ better touch target than a rendered popover on a phone.
252
+ */}
253
+ <div className="flex items-center gap-2 px-2 py-1.5 border-b border-border shrink-0 flex-wrap">
254
+ <select
255
+ value={base}
256
+ onChange={(e) => setBase(e.target.value)}
257
+ aria-label="Base branch"
258
+ data-testid="branch-review-base"
259
+ className="h-7 max-w-[38%] rounded-md border border-border bg-panel px-2 text-xs min-w-0"
260
+ >
261
+ {branches.map((b) => <option key={b.name} value={b.name}>{b.name}</option>)}
262
+ </select>
263
+ <ArrowRight className="size-3.5 text-text-3 shrink-0" />
264
+ <select
265
+ value={head}
266
+ onChange={(e) => setHead(e.target.value)}
267
+ aria-label="Branch to review"
268
+ data-testid="branch-review-head"
269
+ className="h-7 max-w-[38%] rounded-md border border-border bg-panel px-2 text-xs min-w-0"
270
+ >
271
+ {branches.map((b) => <option key={b.name} value={b.name}>{b.name}</option>)}
272
+ </select>
273
+
274
+ <div className="ml-auto flex items-center gap-2">
275
+ <span className="text-xs text-text-2 tabular-nums" data-testid="branch-review-progress">
276
+ {doneCount}/{files.length} reviewed
277
+ </span>
278
+ <Button
279
+ variant="ghost"
280
+ size="icon-xs"
281
+ title={doneCount === files.length && files.length > 0 ? "Clear all" : "Mark all reviewed"}
282
+ disabled={files.length === 0}
283
+ onClick={() => commitReviewed(setAllReviewed(reviewed, files, doneCount !== files.length))}
284
+ >
285
+ <ListChecks className="size-3.5" />
286
+ </Button>
287
+ <Button variant="ghost" size="icon-xs" title="Reload" onClick={() => void loadDiff()}>
288
+ <RefreshCw className={loading ? "animate-spin" : ""} />
289
+ </Button>
290
+ </div>
291
+ </div>
292
+
293
+ {error && (
294
+ <div className="px-3 py-1.5 text-xs text-destructive bg-destructive/10 shrink-0">{error}</div>
295
+ )}
296
+
297
+ <div className="flex-1 flex min-h-0">
298
+ {!isMobile && (
299
+ <div className="w-72 shrink-0 border-r border-border flex flex-col min-h-0">
300
+ {fileList}
301
+ </div>
302
+ )}
303
+
304
+ <div className="flex-1 min-w-0 flex flex-col">
305
+ {selected ? (
306
+ <DiffViewer
307
+ // Remount per file: the viewer keys its fetch off the props it was
308
+ // mounted with, and a shared instance would keep the previous
309
+ // file's contents while the next one loads.
310
+ key={`${selected.path}:${currentDiff?.mergeBase}:${currentDiff?.headCommit}`}
311
+ metadata={{
312
+ filePath: gitRepo.projectFile(selected.path),
313
+ projectName,
314
+ ref1: currentDiff?.mergeBase,
315
+ // The resolved commit, never `head` itself: a ref name moves,
316
+ // and a commit landing mid-review would show the new tip beside
317
+ // a row whose counts and blob id describe the old one.
318
+ ref2: currentDiff?.headCommit,
319
+ }}
320
+ />
321
+ ) : (
322
+ <div className="flex-1 flex items-center justify-center text-xs text-text-3">
323
+ {loading ? <Loader2 className="size-4 animate-spin" /> : "Nothing to review."}
324
+ </div>
325
+ )}
326
+ </div>
327
+ </div>
328
+
329
+ {/* Thumb zone: on a phone the file list and "next" are the two controls
330
+ used constantly, so they sit at the bottom rather than in the header. */}
331
+ {isMobile && (
332
+ <div className="flex items-center gap-2 border-t border-border p-2 shrink-0">
333
+ <Button variant="secondary" size="sm" className="flex-1 min-h-11" onClick={() => setListOpen(true)}>
334
+ <FileText className="size-4" />
335
+ {files.length} files
336
+ </Button>
337
+ <Button variant="secondary" size="sm" className="flex-1 min-h-11" onClick={goToNextUnreviewed}>
338
+ Next unreviewed
339
+ </Button>
340
+ </div>
341
+ )}
342
+
343
+ {isMobile && (
344
+ <BottomSheet open={listOpen} onClose={() => setListOpen(false)}>
345
+ <div className="max-h-[70vh] overflow-hidden flex flex-col">{fileList}</div>
346
+ </BottomSheet>
347
+ )}
348
+
349
+ {!isMobile && selected && (
350
+ <div className="flex items-center gap-2 border-t border-border px-2 py-1 shrink-0">
351
+ <StartEllipsis>{selected.path}</StartEllipsis>
352
+ <Button variant="ghost" size="xs" onClick={goToNextUnreviewed}>
353
+ Next unreviewed
354
+ </Button>
355
+ </div>
356
+ )}
357
+ </div>
358
+ );
359
+ }
360
+
@@ -0,0 +1,138 @@
1
+ /**
2
+ * One row of the branch review tree — a directory or a file.
3
+ *
4
+ * Its own file so it can be *rendered* in a test. Asserting these classes by
5
+ * reading the source back with `readFileSync` passes equally well against a
6
+ * component that never renders at all, and breaks on a `cn()` refactor that
7
+ * changes nothing a user sees; a mounted row survives both.
8
+ *
9
+ * What is pinned here was measured in a real browser at 390x844 before it was:
10
+ * each row came out **24px** tall with a **16px** checkbox, against the 44x44
11
+ * minimum in `docs/design-guidelines.md`. Nothing about that is visible in
12
+ * review — the rows look right on a desktop, which is where the classes were
13
+ * written. Missing the checkbox is worse than missing an ordinary control: the
14
+ * tap falls through to the row and *selects* the file instead of marking it
15
+ * reviewed, which is the opposite action.
16
+ */
17
+ import { Check, ChevronDown, ChevronRight } from "@/lib/icons";
18
+ import { FileIcon } from "@/lib/file-icons";
19
+ import { isReviewed, type ReviewState } from "@/lib/branch-review-state";
20
+ import type { TreeNode } from "@/lib/git-file-tree";
21
+ import type { BranchDiffFile } from "../../../types/git";
22
+
23
+ export const STATUS_COLORS: Record<BranchDiffFile["status"], string> = {
24
+ M: "text-warning",
25
+ A: "text-success",
26
+ D: "text-error",
27
+ R: "text-primary",
28
+ C: "text-accent-2",
29
+ T: "text-text-3",
30
+ };
31
+
32
+ /** Indent per nesting level, matching the Source Control tree. */
33
+ export const TREE_INDENT = 14;
34
+
35
+ /**
36
+ * Ellipsize from the start, so the end of a path — the part that differs —
37
+ * survives. Same trick as the Source Control panel: a right-to-left box cuts at
38
+ * its left edge, and `<bdi>` keeps the name itself reading left to right.
39
+ */
40
+ export function StartEllipsis({ children }: { children: string }) {
41
+ return (
42
+ <span dir="rtl" className="truncate text-left min-w-0 flex-1">
43
+ <bdi>{children}</bdi>
44
+ </span>
45
+ );
46
+ }
47
+
48
+ export interface TreeRowProps {
49
+ node: TreeNode;
50
+ depth: number;
51
+ byPath: Map<string, BranchDiffFile>;
52
+ reviewed: ReviewState;
53
+ selectedPath: string | null;
54
+ collapsed: Set<string>;
55
+ onToggleCollapse: (path: string) => void;
56
+ onSelect: (path: string) => void;
57
+ onToggleReviewed: (file: BranchDiffFile) => void;
58
+ }
59
+
60
+ export function TreeRow(props: TreeRowProps) {
61
+ const { node, depth, byPath, reviewed, selectedPath, collapsed, onToggleCollapse, onSelect, onToggleReviewed } = props;
62
+ const file = node.file ? byPath.get(node.fullPath) : undefined;
63
+
64
+ if (!file) {
65
+ const isCollapsed = collapsed.has(node.fullPath);
66
+ return (
67
+ <>
68
+ <button
69
+ type="button"
70
+ data-testid="branch-review-folder"
71
+ className="w-full flex items-center gap-1 px-2 py-1 text-xs min-h-11 md:min-h-0 hover:bg-surface-hover text-left"
72
+ style={{ paddingLeft: depth * TREE_INDENT + 8 }}
73
+ onClick={() => onToggleCollapse(node.fullPath)}
74
+ >
75
+ {isCollapsed ? <ChevronRight className="size-3 shrink-0" /> : <ChevronDown className="size-3 shrink-0" />}
76
+ <span className="truncate text-text-2">{node.name}</span>
77
+ </button>
78
+ {!isCollapsed && node.children.map((child) => (
79
+ <TreeRow key={child.fullPath} {...props} node={child} depth={depth + 1} />
80
+ ))}
81
+ </>
82
+ );
83
+ }
84
+
85
+ const done = isReviewed(reviewed, file);
86
+ const isSelected = selectedPath === file.path;
87
+ return (
88
+ <div
89
+ // The selected row may not share `--surface-hover` with the hover state:
90
+ // on a pointer device every row looks selected while the cursor is over
91
+ // it, and which file the diff pane is actually showing becomes a guess.
92
+ // `aria-current` carries the same fact for anything not reading colour.
93
+ className={`flex items-center gap-1.5 pr-2 py-1 text-xs cursor-pointer border-l-2 min-h-11 md:min-h-0 hover:bg-surface-hover ${
94
+ isSelected ? "bg-primary/10 border-primary" : "border-transparent"
95
+ }`}
96
+ style={{ paddingLeft: depth * TREE_INDENT + 6 }}
97
+ onClick={() => onSelect(file.path)}
98
+ aria-current={isSelected ? "true" : undefined}
99
+ data-testid="branch-review-file"
100
+ data-path={file.path}
101
+ data-selected={isSelected ? "true" : "false"}
102
+ data-reviewed={done ? "true" : "false"}
103
+ >
104
+ {/* A real button inside the row, so the tap that reviews a file and the
105
+ tap that opens it never have to be told apart by a timer. */}
106
+ <button
107
+ type="button"
108
+ aria-label={done ? `Mark ${file.path} unreviewed` : `Mark ${file.path} reviewed`}
109
+ data-testid="branch-review-check"
110
+ // The tap area is 44px on touch and the drawn box stays 16px: a
111
+ // checkbox the size of the glyph is the control most often missed, and
112
+ // missing it here selects the file instead — the opposite action.
113
+ className="size-11 md:size-4 shrink-0 flex items-center justify-center"
114
+ onClick={(e) => { e.stopPropagation(); onToggleReviewed(file); }}
115
+ >
116
+ <span
117
+ data-testid="branch-review-check-box"
118
+ className={`size-4 rounded border flex items-center justify-center ${
119
+ done ? "bg-primary border-primary text-primary-foreground" : "border-border"
120
+ }`}
121
+ >
122
+ {done && <Check className="size-3" />}
123
+ </span>
124
+ </button>
125
+ <FileIcon name={node.name} className="size-3.5 shrink-0" />
126
+ <StartEllipsis>{node.name}</StartEllipsis>
127
+ <span className={`shrink-0 font-mono ${STATUS_COLORS[file.status]}`}>{file.status}</span>
128
+ {file.binary ? (
129
+ <span className="shrink-0 text-text-3 italic">bin</span>
130
+ ) : (
131
+ <span className="shrink-0 tabular-nums text-[10px]">
132
+ <span className="text-success">+{file.additions}</span>{" "}
133
+ <span className="text-error">-{file.deletions}</span>
134
+ </span>
135
+ )}
136
+ </div>
137
+ );
138
+ }
@@ -11,6 +11,23 @@ interface AttachmentChipsProps {
11
11
  onRemove: (id: string) => void;
12
12
  }
13
13
 
14
+ /**
15
+ * What a click on the chip *body* should do.
16
+ *
17
+ * A named function rather than an inline condition because the bug it fixes was
18
+ * the condition itself: the handler asked only about `textContent`, which an
19
+ * image never has, so the chip did nothing for exactly the attachment you most
20
+ * want to look at before sending. This suite has no DOM renderer, so a decision
21
+ * left inline here is untestable by construction.
22
+ *
23
+ * The remove button is not a case: it stops the event before it gets here.
24
+ */
25
+ export function chipBodyAction(att: ChatAttachment): "expand" | "preview" | "none" {
26
+ if (att.textContent) return "expand";
27
+ if (att.previewUrl) return "preview";
28
+ return "none";
29
+ }
30
+
14
31
  export function AttachmentChips({ attachments, onRemove }: AttachmentChipsProps) {
15
32
  const [expandedId, setExpandedId] = useState<string | null>(null);
16
33
  const coarse = usePrefersCoarsePointer();
@@ -82,18 +99,25 @@ export function AttachmentChips({ attachments, onRemove }: AttachmentChipsProps)
82
99
  key={att.id}
83
100
  className={cn(
84
101
  "flex items-center gap-1.5 rounded-md border border-border bg-surface px-2 py-1 text-xs text-text-secondary max-w-48",
85
- att.textContent && "cursor-pointer hover:border-primary/50",
102
+ chipBodyAction(att) !== "none" && "cursor-pointer hover:border-primary/50",
86
103
  expandedId === att.id && "border-primary/50 bg-surface-elevated",
87
104
  )}
88
- onClick={() => {
89
- if (att.textContent) setExpandedId(expandedId === att.id ? null : att.id);
105
+ // The whole chip opens the preview, not just the 20px thumbnail — the
106
+ // filename beside it is most of the chip's width and reads as part of
107
+ // the same control, so clicking it doing nothing is the bug. Only the
108
+ // remove button opts out, which it already does by stopping the event.
109
+ onClick={(e) => {
110
+ const action = chipBodyAction(att);
111
+ if (action === "expand") setExpandedId(expandedId === att.id ? null : att.id);
112
+ else if (action === "preview") preview(att, e.currentTarget);
90
113
  }}
91
114
  >
92
115
  {/* Thumbnail or icon */}
93
116
  {att.previewUrl ? (
94
- // A real button, not a click handler on the chip: the chip already carries the
95
- // remove button, and a button inside a button is invalid. It also means the
96
- // preview is reachable by keyboard, which the chip never was.
117
+ // Still a real button even though the chip around it now opens the same
118
+ // preview, and for the one thing the chip cannot do: reach it by keyboard.
119
+ // The chip has to stay a `div` — it carries the remove button, and a button
120
+ // inside a button is invalid — so this is the only focusable way in.
97
121
  //
98
122
  // The visible thumbnail stays 20px and only the tap-registering area grows,
99
123
  // through the same invisible `::before` the explorer toolbar uses — a chip
@@ -125,6 +125,17 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
125
125
  });
126
126
  }, [sessionId, providerId, permissionMode]); // eslint-disable-line react-hooks/exhaustive-deps
127
127
 
128
+ /**
129
+ * The tab's icon is this provider's logo, and the effect above only persists
130
+ * the provider once a session exists — which is after the provider can no
131
+ * longer be changed. Without this, a Codex or Cursor chat wears the Claude
132
+ * logo for as long as its first message is being written.
133
+ */
134
+ const handleProviderChange = useCallback((id: string) => {
135
+ setProviderId(id);
136
+ if (tabId) updateTab(tabId, { metadata: { ...metadata, providerId: id } });
137
+ }, [tabId, metadata, updateTab]);
138
+
128
139
  /** The account this tab claimed while it had no session yet, and who it was claimed from. */
129
140
  const pickedAccountId = metadata?.pickedAccountId as string | undefined;
130
141
  const pickedAccountLabel = metadata?.pickedAccountLabel as string | undefined;
@@ -900,7 +911,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
900
911
  onModeChange={setPermissionMode}
901
912
  providerId={providerId}
902
913
  sessionId={sessionId ?? undefined}
903
- onProviderChange={!sessionId ? setProviderId : undefined}
914
+ onProviderChange={!sessionId ? handleProviderChange : undefined}
904
915
  model={model}
905
916
  onModelChange={setModel}
906
917
  effort={effort}