@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.
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +29 -4
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/bun.lock +16 -1
- package/dist/web/assets/accounts-settings-section-D3BylchP.js +1 -0
- package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-BLPcnhTM.js} +1 -1
- package/dist/web/assets/ai-settings-section-BtR7rF-T.js +1 -0
- package/dist/web/assets/ai-settings-section-r9JctHNM.js +1 -0
- package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
- package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
- package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
- package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
- package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
- package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
- package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-CiMdk3sb.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
- package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
- package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
- package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
- package/dist/web/assets/branch-review-tab-B1pFjlKM.js +1 -0
- package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
- package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
- package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
- package/dist/web/assets/chat-tab-DZagX06m.js +15 -0
- package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
- package/dist/web/assets/code-editor-BJhOEsnZ.js +7 -0
- package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-CaCQJ4r_.js} +1 -1
- package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
- package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-B7u_p49h.js} +3 -3
- package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
- package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
- package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
- package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-CttjJU1e.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
- package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-Ca4HxIfx.js} +1 -1
- package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
- package/dist/web/assets/diff-viewer-9-XjLIwa.js +4 -0
- package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
- package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
- package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-D0DuEOs_.js} +1 -1
- package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
- package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
- package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
- package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
- package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
- package/dist/web/assets/explorer-body-VdCBqcZu.js +2 -0
- package/dist/web/assets/explorer-window-content-BIsbl7Th.js +1 -0
- package/dist/web/assets/extension-manager-section-HkY7geS5.js +1 -0
- package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-DZG8YHX9.js} +1 -1
- package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
- package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
- package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
- package/dist/web/assets/files-settings-section-qnRtkX05.js +1 -0
- package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
- package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
- package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
- package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-BAFSZmCE.js} +1 -1
- package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
- package/dist/web/assets/git-log-panel-2sGpfEla.js +1 -0
- package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
- package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
- package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-f0hyITiF.js} +7 -7
- package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
- package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
- package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
- package/dist/web/assets/group-chat-tab-Ck6sk7yY.js +1 -0
- package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
- package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
- package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
- package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
- package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
- package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
- package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
- package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
- package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
- package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
- package/dist/web/assets/index-BH99TIE0.css +1 -0
- package/dist/web/assets/index-CEZVxM4G.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
- package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
- package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-CeLmhvu1.js} +1 -1
- package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
- package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
- package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
- package/dist/web/assets/keybindings-store-C_3ChOEf.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-PVz6kuh2.js} +1 -1
- package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
- package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
- package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CvL1pWeK.js → markdown-renderer-C140Ijk0.js} +3 -3
- package/dist/web/assets/markdown-renderer-Dz7-DYUg.js +1 -0
- package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
- package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
- package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CUG_owdf.js +1 -0
- package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
- package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
- package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
- package/dist/web/assets/notification-store-6vib1ISy.js +1 -0
- package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
- package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
- package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
- package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
- package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
- package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
- package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
- package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-Cji-Mil-.js} +1 -1
- package/dist/web/assets/problems-panel-a6_hrWvl.js +1 -0
- package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-CiIqlOIK.js} +1 -1
- package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
- package/dist/web/assets/project-store-B87hLRSk.js +1 -0
- package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-BjI4IFgp.js} +1 -1
- package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
- package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
- package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
- package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-eXYvwgx7.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-Doh-fci-.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-Dt8Akc3p.js +1 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-t83fqbID.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-CguhZMmx.js +1 -0
- package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
- package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
- package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-BVkpW5ms.js} +1 -1
- package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
- package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
- package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
- package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
- package/dist/web/assets/settings-body-CoIxYAh6.js +2 -0
- package/dist/web/assets/settings-store-krC6bc79.js +2 -0
- package/dist/web/assets/settings-tab-7DKVUTbM.js +1 -0
- package/dist/web/assets/settings-window-content-3_39TeX-.js +1 -0
- package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
- package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
- package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
- package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
- package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-CQB7Yp7U.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-Bd7rjr1D.js} +1 -1
- package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
- package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
- package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
- package/dist/web/assets/system-monitor-body-DsRXeMyU.js +2 -0
- package/dist/web/assets/system-monitor-tab-DdSZgux3.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-ClPo4-El.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-d2TAD4PU.js} +1 -1
- package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
- package/dist/web/assets/team-member-sheet-7d2Qp-ie.js +2 -0
- package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-Bctg2gmg.js} +1 -1
- package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
- package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-0HQic25V.js} +3 -3
- package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
- package/dist/web/assets/tool-cards-CmLLRACd.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
- package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
- package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
- package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
- package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-sDMjcz1-.js} +3 -3
- package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CBgID-EU.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-gvhJ_jZh.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
- package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-C9wlRliB.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
- package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
- package/dist/web/assets/video-preview-CSDK5j6H.js +1 -0
- package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
- package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
- package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
- package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
- package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
- package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
- package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
- package/dist/web/index.html +27 -26
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +9 -0
- package/docs/architecture/plugins-and-tracing.md +285 -0
- package/docs/lessons-learned.md +31 -0
- package/docs/system-architecture.md +1 -0
- package/package.json +2 -1
- package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
- package/packages/ext-git-graph/src/compare-view.ts +22 -3
- package/packages/ext-git-graph/src/extension.ts +6 -0
- package/packages/ext-git-graph/src/types.ts +19 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +159 -1
- package/packages/ext-git-graph/src/webview-html.ts +344 -26
- package/scripts/gen-file-icons.ts +493 -242
- package/src/providers/claude-agent-sdk.ts +42 -1
- package/src/providers/codex-app-server/codex-history.ts +77 -23
- package/src/server/index.ts +42 -7
- package/src/server/routes/chat.ts +15 -11
- package/src/server/routes/git.ts +83 -3
- package/src/server/routes/remote-desktop.ts +7 -2
- package/src/server/ws/global.ts +1 -1
- package/src/server/ws/remote-desktop.ts +12 -5
- package/src/services/binary-content.ts +25 -0
- package/src/services/chat-search.service.ts +1 -1
- package/src/services/chat.service.ts +9 -0
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/event-loop-lag.ts +208 -0
- package/src/services/file-lines.ts +114 -0
- package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
- package/src/services/file-watcher/watch-tree.ts +131 -10
- package/src/services/file-watcher.service.ts +73 -23
- package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
- package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
- package/src/services/git-hunks/git-hunks.service.ts +26 -11
- package/src/services/git-hunks/unified-diff.ts +15 -1
- package/src/services/git.service.ts +74 -24
- package/src/services/jsonl-transcript-parser.ts +138 -6
- package/src/services/log-rotate.ts +142 -0
- package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
- package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
- package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
- package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
- package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
- package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
- package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
- package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
- package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
- package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
- package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
- package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
- package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
- package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
- package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
- package/src/services/supervisor.ts +47 -4
- package/src/services/windows-process-tree.ts +351 -351
- package/src/shared/codex-daily-guard.ts +12 -5
- package/src/shared/remote-desktop-custom-quality.ts +117 -0
- package/src/shared/remote-desktop-quality.ts +127 -0
- package/src/types/chat.ts +4 -0
- package/src/types/git.ts +52 -0
- package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
- package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
- package/src/web/components/chat/attachment-chips.tsx +30 -6
- package/src/web/components/chat/chat-tab.tsx +12 -1
- package/src/web/components/chat/message-events.tsx +273 -0
- package/src/web/components/chat/message-list.tsx +107 -861
- package/src/web/components/chat/message-markdown.tsx +34 -0
- package/src/web/components/chat/message-media.tsx +174 -0
- package/src/web/components/chat/message-user-bubble.tsx +399 -0
- package/src/web/components/chat/provider-selector.tsx +30 -22
- package/src/web/components/chat/session-history-list.tsx +2 -2
- package/src/web/components/chat/session-list-panel.tsx +3 -2
- package/src/web/components/editor/binary-diff-view.tsx +212 -0
- package/src/web/components/editor/diff-viewer.tsx +76 -3
- package/src/web/components/explorer/file-tree.tsx +235 -134
- package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
- package/src/web/components/explorer/tree-node.tsx +69 -69
- package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
- package/src/web/components/extensions/extension-tree-view.tsx +4 -11
- package/src/web/components/git/git-status-panel.tsx +25 -0
- package/src/web/components/layout/command-palette.tsx +2 -18
- package/src/web/components/layout/draggable-tab.tsx +4 -2
- package/src/web/components/layout/nav-section-rail.tsx +12 -3
- package/src/web/components/layout/status-bar.tsx +10 -6
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/tab-pool.tsx +1 -0
- package/src/web/components/layout/upgrade-button.tsx +5 -1
- package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
- package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
- package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
- package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
- package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
- package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
- package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
- package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
- package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
- package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
- package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
- package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
- package/src/web/components/settings/accounts/account-card.tsx +2 -2
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +4 -4
- package/src/web/components/settings/extension-manager-section.tsx +8 -1
- package/src/web/components/settings/theme-picker.tsx +2 -1
- package/src/web/components/ui/tooltip.tsx +15 -2
- package/src/web/lib/branch-review-state.ts +175 -0
- package/src/web/lib/extension-icons.ts +70 -0
- package/src/web/lib/file-icon-name.ts +62 -0
- package/src/web/lib/file-icons.generated.ts +3287 -25
- package/src/web/lib/file-icons.tsx +71 -73
- package/src/web/lib/flatten-expansions.ts +18 -5
- package/src/web/lib/message-ordinals.ts +23 -0
- package/src/web/lib/provider-logos.tsx +143 -0
- package/src/web/lib/tab-type-icons.ts +25 -0
- package/src/web/stores/file-store.ts +73 -35
- package/src/web/stores/panel-utils.ts +5 -0
- package/src/web/stores/project-framework-store.ts +82 -0
- package/src/web/stores/project-store.ts +7 -1
- package/src/web/stores/settings-store.ts +128 -0
- package/src/web/stores/tab-store.ts +1 -0
- package/src/web/stores/window-panel-persistence.ts +1 -0
- package/src/web/styles/file-icons.generated.css +3282 -234
- package/src/web/styles/globals.css +26 -0
- package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
- package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
- package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
- package/dist/web/assets/api-client-BUCo1lms.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
- package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
- package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
- package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
- package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
- package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
- package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
- package/dist/web/assets/extension-manager-section-BhwB6nqr.js +0 -1
- package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
- package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
- package/dist/web/assets/file-store-DMABxOkV.js +0 -1
- package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
- package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
- package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
- package/dist/web/assets/index-D7DbXD3G.css +0 -1
- package/dist/web/assets/index-ZtfxZOyb.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
- package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
- package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
- package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
- package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
- package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
- package/dist/web/assets/project-store-BhMbVln3.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
- package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
- package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
- package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
- package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
- package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
- package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
- package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
- package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
- package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
- package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
- package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
- package/dist/web/assets/ws-auth-DEAJiGCR.js +0 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- /package/dist/web/assets/{abap-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
- /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
- /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
- /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
- /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
- /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
- /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
- /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
- /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
- /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
- /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
- /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
- /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
- /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
- /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
- /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
- /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
- /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
- /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
- /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
- /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
- /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
- /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
- /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
- /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
- /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
- /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
- /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
- /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
- /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
- /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
- /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
- /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
- /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
- /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
- /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
- /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
- /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
- /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
- /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
- /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
- /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
- /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
- /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
- /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
- /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
- /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
- /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
- /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
- /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
- /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
- /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
- /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
- /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
- /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
- /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
- /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
- /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
- /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
- /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
- /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
- /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
- /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
- /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
- /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
- /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
- /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
- /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
- /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
- /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
- /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
- /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
- /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
- /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
- /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
- /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
- /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
- /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
- /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
- /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
- /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
- /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
- /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
- /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
- /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
- /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
- /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
- /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
- /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
- /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
- /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
- /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
- /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
- /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
- /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
- /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
- /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
- /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
- /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
- /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
- /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
- /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
- /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
- /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
- /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
- /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
- /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
- /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
- /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
- /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
- /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
- /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
- /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
- /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
- /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
- /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
- /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
- /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
- /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
- /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
- /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
- /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
- /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
- /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
- /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
- /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
- /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
- /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
- /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
- /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
- /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
- /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
- /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
- /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
- /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
- /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
- /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
- /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
- /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
- /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
- /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
- /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
- /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
- /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
- /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
- /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
- /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
- /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
- /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
- /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
- /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
- /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
- /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
- /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
- /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
- /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
- /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
- /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
- /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
- /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
- /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
- /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
- /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
- /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
- /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
- /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
- /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
- /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
- /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
- /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
- /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
- /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
- /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
- /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
- /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
- /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
- /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
- /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
- /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
- /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
- /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
- /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
- /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
- /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
- /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
- /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
- /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
- /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
- /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
- /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
- /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
- /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
- /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
- /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
- /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
- /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
- /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
- /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
- /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
- /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
- /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
- /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
- /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
- /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
- /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
|
|
102
|
+
chipBodyAction(att) !== "none" && "cursor-pointer hover:border-primary/50",
|
|
86
103
|
expandedId === att.id && "border-primary/50 bg-surface-elevated",
|
|
87
104
|
)}
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
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 ?
|
|
914
|
+
onProviderChange={!sessionId ? handleProviderChange : undefined}
|
|
904
915
|
model={model}
|
|
905
916
|
onModelChange={setModel}
|
|
906
917
|
effort={effort}
|