@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
|
@@ -14,25 +14,21 @@
|
|
|
14
14
|
* nodes for React to reconcile on every expand. And a row is draggable — an
|
|
15
15
|
* inner `<img>` supplies its own drag image and has to be talked out of it.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Name resolution is next door in `file-icon-name.ts`, which is pure and
|
|
18
|
+
* testable; this file is the drawing and the one subscription that decides
|
|
19
|
+
* whether `.service.ts` is a Nest provider or an Angular service.
|
|
20
|
+
* `fileIconElement` is for the slots that want a component rather than an
|
|
21
|
+
* element — the tab bar and the palette both take an `icon: ElementType`.
|
|
21
22
|
*/
|
|
22
23
|
import type { FC } from "react";
|
|
23
24
|
// `?url` gives the built stylesheet's address without putting it on the module
|
|
24
25
|
// graph: nothing is fetched until the `<link>` below is appended.
|
|
25
26
|
import ICON_CSS_URL from "@/styles/file-icons.generated.css?url";
|
|
26
27
|
import { basename, cn } from "@/lib/utils";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
EXTENSION_ICONS,
|
|
32
|
-
FILENAME_ICONS,
|
|
33
|
-
FOLDER_ICONS,
|
|
34
|
-
FOLDER_OPEN_ICONS,
|
|
35
|
-
} from "./file-icons.generated";
|
|
28
|
+
import { fileIconName, folderIconName } from "./file-icon-name";
|
|
29
|
+
import { useIconFramework } from "@/stores/project-framework-store";
|
|
30
|
+
|
|
31
|
+
export { fileIconName, folderIconName };
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* The artwork is fetched when something first asks for an icon, not before.
|
|
@@ -40,10 +36,16 @@ import {
|
|
|
40
36
|
* Imported at module scope it was not lazy in any useful sense: this module is
|
|
41
37
|
* reached from `tab-type-icons.ts`, which the tab bar, the mobile nav and the
|
|
42
38
|
* dock header all import eagerly, so Vite hoisted the stylesheet into a
|
|
43
|
-
* `<link rel="stylesheet">` in `index.html` — render-blocking on every load
|
|
44
|
-
* measured
|
|
45
|
-
*
|
|
46
|
-
*
|
|
39
|
+
* `<link rel="stylesheet">` in `index.html` — render-blocking on every load.
|
|
40
|
+
* Re-measured against this branch's full 1193-glyph port rather than the 224 it
|
|
41
|
+
* was first written for: **1,952,181 bytes raw / 535,335 gzip / 391,418 brotli**
|
|
42
|
+
* against the app's entire other stylesheet at 26,340 gzip, so the icons are
|
|
43
|
+
* **20x** everything else put together. (The figure quoted here used to be
|
|
44
|
+
* 499,375 raw / 4.9x, which was the truth at 224 glyphs and is a quarter of the
|
|
45
|
+
* truth now.) It is a first-*use* cost rather than a first-load one, which is
|
|
46
|
+
* the whole point of the boundary — but a phone that opens one file listing
|
|
47
|
+
* downloads 382 KiB of artwork to do it, and that number belongs in the comment
|
|
48
|
+
* rather than in someone's memory.
|
|
47
49
|
*
|
|
48
50
|
* So it is fetched at runtime by a `<link>` this function appends. The cost is
|
|
49
51
|
* honest and visible: an icon that renders before the stylesheet lands is a
|
|
@@ -84,40 +86,6 @@ function requestIconCss(): void {
|
|
|
84
86
|
document.head.appendChild(link);
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
/** The icon name for a file, by name alone. */
|
|
88
|
-
export function fileIconName(path: string): string {
|
|
89
|
-
const name = basename(path).toLowerCase();
|
|
90
|
-
const byName = FILENAME_ICONS[name];
|
|
91
|
-
if (byName) return byName;
|
|
92
|
-
|
|
93
|
-
const parts = name.split(".");
|
|
94
|
-
if (parts.length > 2) {
|
|
95
|
-
// `.spec.ts`, `.d.ts`, `.config.js` — the theme gives these their own
|
|
96
|
-
// glyphs, and matching only the last extension would lose them.
|
|
97
|
-
const double = `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
|
|
98
|
-
const byDouble = EXTENSION_ICONS[double];
|
|
99
|
-
if (byDouble) return byDouble;
|
|
100
|
-
}
|
|
101
|
-
if (parts.length > 1) {
|
|
102
|
-
const byExt = EXTENSION_ICONS[parts[parts.length - 1]!];
|
|
103
|
-
if (byExt) return byExt;
|
|
104
|
-
}
|
|
105
|
-
// A dotfile with no extension (`.gitignore` handled above, `.foorc` not) has
|
|
106
|
-
// its name as its only extension.
|
|
107
|
-
if (name.startsWith(".")) {
|
|
108
|
-
const byDot = EXTENSION_ICONS[name.slice(1)];
|
|
109
|
-
if (byDot) return byDot;
|
|
110
|
-
}
|
|
111
|
-
return DEFAULT_FILE_ICON;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** The icon name for a folder, open or closed. */
|
|
115
|
-
export function folderIconName(path: string, open = false): string {
|
|
116
|
-
const name = basename(path).toLowerCase();
|
|
117
|
-
const table = open ? FOLDER_OPEN_ICONS : FOLDER_ICONS;
|
|
118
|
-
return table[name] ?? (open ? DEFAULT_FOLDER_OPEN_ICON : DEFAULT_FOLDER_ICON);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
89
|
export type FileIconKind = "file" | "directory";
|
|
122
90
|
|
|
123
91
|
export interface FileIconProps {
|
|
@@ -140,19 +108,28 @@ export interface FileIconProps {
|
|
|
140
108
|
* blockify it for free; the tab strip wraps its icon in a `<span class="relative">`
|
|
141
109
|
* for the notification dot, and there the icon simply did not render — the one
|
|
142
110
|
* place an inline `<svg>` would have worked without saying so.
|
|
111
|
+
*
|
|
112
|
+
* The framework overlay is read here rather than threaded through as a prop
|
|
113
|
+
* because there are eight call sites and two of them — the tab bar and the
|
|
114
|
+
* command palette — go through `fileIconElement`, which hands out a *component*
|
|
115
|
+
* and has no project in scope at all. The subscription costs nothing: the tree
|
|
116
|
+
* is virtualised, so only the ~40 visible rows are mounted, and `TreeRow`
|
|
117
|
+
* already reads four stores.
|
|
143
118
|
*/
|
|
144
|
-
function iconClass(icon: string, className?: string): string {
|
|
145
|
-
return cn(
|
|
146
|
-
"inline-block shrink-0 size-4 bg-center bg-no-repeat bg-contain",
|
|
147
|
-
`vsi-${icon}`,
|
|
148
|
-
className,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
119
|
export function FileIcon({ name, kind = "file", open, className }: FileIconProps) {
|
|
153
120
|
requestIconCss();
|
|
154
|
-
const
|
|
155
|
-
|
|
121
|
+
const framework = useIconFramework();
|
|
122
|
+
const icon = kind === "directory" ? folderIconName(name, open) : fileIconName(name, framework);
|
|
123
|
+
return (
|
|
124
|
+
<span
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
className={cn(
|
|
127
|
+
"inline-block shrink-0 size-4 bg-center bg-no-repeat bg-contain",
|
|
128
|
+
`vsi-${icon}`,
|
|
129
|
+
className,
|
|
130
|
+
)}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
156
133
|
}
|
|
157
134
|
|
|
158
135
|
/**
|
|
@@ -163,14 +140,29 @@ export function FileIcon({ name, kind = "file", open, className }: FileIconProps
|
|
|
163
140
|
* function per render would unmount and remount the node on every keystroke in
|
|
164
141
|
* the palette's filter.
|
|
165
142
|
*
|
|
166
|
-
* Keyed by the
|
|
167
|
-
*
|
|
168
|
-
* entries rendering the same span,
|
|
169
|
-
* as many keys as it
|
|
170
|
-
* rule
|
|
171
|
-
*
|
|
172
|
-
*
|
|
143
|
+
* Keyed by the *basename*, not the whole path, because the basename is all
|
|
144
|
+
* `fileIconName` reads. A path key made `src/a/index.ts` and `src/b/index.ts`
|
|
145
|
+
* two entries rendering the same span, so a repository indexed by the palette
|
|
146
|
+
* had as many keys as it had files — which is why the key needed an eviction
|
|
147
|
+
* rule at all. It cannot be keyed by the resolved glyph: `FileIcon` subscribes
|
|
148
|
+
* to the project's framework preset, so the glyph a name resolves to changes
|
|
149
|
+
* under it and a precomputed one would stop following.
|
|
150
|
+
*
|
|
151
|
+
* Eviction drops the oldest quarter rather than calling `clear()`. A `Map`
|
|
152
|
+
* keeps insertion order, and clearing hands every caller a new component type
|
|
153
|
+
* at once — remounting every icon on screen, which is the one thing this cache
|
|
154
|
+
* exists to prevent.
|
|
155
|
+
*
|
|
156
|
+
* Insertion order, not recency: this is deliberately *not* an LRU, and the
|
|
157
|
+
* difference is visible exactly once. Scrolling back through a tree with more
|
|
158
|
+
* than 2000 distinct basenames can evict a name that is still on screen —
|
|
159
|
+
* whichever was cached first, however recently it was used — and remount those
|
|
160
|
+
* icons one time. Re-entering them puts them back at the end of the order.
|
|
161
|
+
* Keeping a real LRU would mean touching the Map on every `fileIconElement`
|
|
162
|
+
* call, which the palette makes hundreds of per keystroke, to avoid one
|
|
163
|
+
* remount in a tree that size.
|
|
173
164
|
*/
|
|
165
|
+
const ELEMENT_CACHE_MAX = 2000;
|
|
174
166
|
const elementCache = new Map<string, FC<{ className?: string }>>();
|
|
175
167
|
|
|
176
168
|
export function fileIconElement(
|
|
@@ -180,13 +172,19 @@ export function fileIconElement(
|
|
|
180
172
|
// The palette asks for hundreds of these before any of them renders, so the
|
|
181
173
|
// fetch starts here too rather than waiting for the first mount.
|
|
182
174
|
requestIconCss();
|
|
183
|
-
const
|
|
184
|
-
const
|
|
175
|
+
const base = basename(name).toLowerCase();
|
|
176
|
+
const key = `${kind}:${base}`;
|
|
177
|
+
const cached = elementCache.get(key);
|
|
185
178
|
if (cached) return cached;
|
|
186
179
|
const Bound: FC<{ className?: string }> = ({ className }) => (
|
|
187
|
-
<
|
|
180
|
+
<FileIcon name={name} kind={kind} className={className} />
|
|
188
181
|
);
|
|
189
|
-
Bound.displayName = `FileIcon(${
|
|
190
|
-
elementCache.
|
|
182
|
+
Bound.displayName = `FileIcon(${base})`;
|
|
183
|
+
if (elementCache.size >= ELEMENT_CACHE_MAX) {
|
|
184
|
+
for (const stale of [...elementCache.keys()].slice(0, ELEMENT_CACHE_MAX >> 2)) {
|
|
185
|
+
elementCache.delete(stale);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
elementCache.set(key, Bound);
|
|
191
189
|
return Bound;
|
|
192
190
|
}
|
|
@@ -27,10 +27,23 @@ export function flattenWithExpansions(
|
|
|
27
27
|
): ChatMessage[] {
|
|
28
28
|
if (expansions.size === 0) return messages;
|
|
29
29
|
const out: ChatMessage[] = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// Recursive, because each expansion now arrives as one compaction segment
|
|
31
|
+
// headed by the summary *before* it — so expanding that one puts its messages
|
|
32
|
+
// under a key that only appears inside another expansion. A flat pass would
|
|
33
|
+
// fetch them and render nothing, which looks exactly like the load failing.
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
const visit = (list: ChatMessage[]): void => {
|
|
36
|
+
for (const m of list) {
|
|
37
|
+
const pre = m.id ? expansions.get(m.id) : undefined;
|
|
38
|
+
// `seen` guards the walk: a key that somehow resolved to a list
|
|
39
|
+
// containing itself would otherwise recurse until the stack gave out.
|
|
40
|
+
if (pre && pre.length > 0 && m.id && !seen.has(m.id)) {
|
|
41
|
+
seen.add(m.id);
|
|
42
|
+
visit(pre);
|
|
43
|
+
}
|
|
44
|
+
out.push(m);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
visit(messages);
|
|
35
48
|
return out;
|
|
36
49
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ChatMessage } from "../../types/chat";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The 1-based position of each user message among the user messages, with 0 for
|
|
5
|
+
* every other role. It is the stable anchor a version group is keyed on, so a
|
|
6
|
+
* fork keeps pointing at the same turn.
|
|
7
|
+
*
|
|
8
|
+
* Counted in one pass and kept here rather than in the transcript component:
|
|
9
|
+
* the obvious per-row form is a prefix `slice().reduce()`, which is O(n²) and
|
|
10
|
+
* costs nothing at the couple of hundred messages a live session shows — then
|
|
11
|
+
* roughly six million operations per render once a long session's history has
|
|
12
|
+
* been lazily scrolled in, which is precisely when it must not.
|
|
13
|
+
*/
|
|
14
|
+
export function userMessageOrdinals(messages: Pick<ChatMessage, "role">[]): number[] {
|
|
15
|
+
const out = new Array<number>(messages.length);
|
|
16
|
+
let seen = 0;
|
|
17
|
+
for (let i = 0; i < messages.length; i++) {
|
|
18
|
+
const isUser = messages[i]!.role === "user";
|
|
19
|
+
if (isUser) seen++;
|
|
20
|
+
out[i] = isUser ? seen : 0;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI providers' own logos, for every surface that marks a chat with the
|
|
3
|
+
* provider running it.
|
|
4
|
+
*
|
|
5
|
+
* Artwork from `@lobehub/icons-static-svg@1.95.0` (MIT, © LobeHub —
|
|
6
|
+
* github.com/lobehub/lobe-icons), copied in rather than depended on: four
|
|
7
|
+
* glyphs do not carry a package of a thousand SVGs. To change one, re-fetch
|
|
8
|
+
* that exact version. This is brand artwork and a third system beside the two
|
|
9
|
+
* that already exist — the app's own chrome is `@/lib/icons` (Fluent) and file
|
|
10
|
+
* artwork is `@/lib/file-icons` (vscode-icons).
|
|
11
|
+
*
|
|
12
|
+
* Two things here are load-bearing rather than stylistic.
|
|
13
|
+
*
|
|
14
|
+
* A gradient is referenced by id, and every copy of a logo would otherwise
|
|
15
|
+
* declare the *same* id. `url(#id)` resolves against the first such element in
|
|
16
|
+
* the document, and a gradient defined inside a `display: none` subtree paints
|
|
17
|
+
* nothing — which in PPM is the normal case, because hidden tabs stay mounted.
|
|
18
|
+
* One chat tab in the background is enough to blank the Codex and Gemini marks
|
|
19
|
+
* everywhere else on the page, with no error anywhere. So every instance mints
|
|
20
|
+
* its own ids with `useId` (React 19.2 returns `_r_0_`-shaped ids, which are
|
|
21
|
+
* valid inside `url(#…)`).
|
|
22
|
+
*
|
|
23
|
+
* Codex's *colour* file is an app tile — a white rounded square with the mark
|
|
24
|
+
* inset at 75% — and a white square is a hole punched in every dark theme. The
|
|
25
|
+
* tile is dropped; the mono file's full-size geometry takes the tile's gradient
|
|
26
|
+
* instead, so the mark fills the same box as its neighbours.
|
|
27
|
+
*/
|
|
28
|
+
import { useId, type ReactElement, type SVGProps } from "react";
|
|
29
|
+
|
|
30
|
+
export type ProviderLogoProps = SVGProps<SVGSVGElement>;
|
|
31
|
+
export type ProviderLogo = (props: ProviderLogoProps) => ReactElement;
|
|
32
|
+
|
|
33
|
+
function Logo({ name, children, ...rest }: ProviderLogoProps & { name: string }) {
|
|
34
|
+
return (
|
|
35
|
+
<svg
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
width={24}
|
|
39
|
+
height={24}
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
focusable="false"
|
|
42
|
+
// Which logo this is, readable from rendered markup and in devtools —
|
|
43
|
+
// the same hook `fluent-icon.tsx` gives the product icons via `data-icon`,
|
|
44
|
+
// and for the same reason: no class name for stylesheets to depend on.
|
|
45
|
+
data-provider-logo={name}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</svg>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function ClaudeLogo(props: ProviderLogoProps) {
|
|
54
|
+
return (
|
|
55
|
+
<Logo name="claude" {...props}>
|
|
56
|
+
<path
|
|
57
|
+
d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"
|
|
58
|
+
fill="#D97757"
|
|
59
|
+
/>
|
|
60
|
+
</Logo>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function CodexLogo(props: ProviderLogoProps) {
|
|
65
|
+
const gradient = `${useId()}codex`;
|
|
66
|
+
return (
|
|
67
|
+
<Logo name="codex" {...props}>
|
|
68
|
+
<defs>
|
|
69
|
+
<linearGradient id={gradient} gradientUnits="userSpaceOnUse" x1="12" x2="12" y1="0" y2="24">
|
|
70
|
+
<stop stopColor="#B1A7FF" />
|
|
71
|
+
<stop offset=".5" stopColor="#7A9DFF" />
|
|
72
|
+
<stop offset="1" stopColor="#3941FF" />
|
|
73
|
+
</linearGradient>
|
|
74
|
+
</defs>
|
|
75
|
+
<path
|
|
76
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
77
|
+
fill={`url(#${gradient})`}
|
|
78
|
+
fillRule="evenodd"
|
|
79
|
+
clipRule="evenodd"
|
|
80
|
+
/>
|
|
81
|
+
</Logo>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Cursor publishes no colour variant — the mark is monochrome by design. */
|
|
86
|
+
export function CursorLogo(props: ProviderLogoProps) {
|
|
87
|
+
return (
|
|
88
|
+
<Logo name="cursor" fill="currentColor" {...props}>
|
|
89
|
+
<path
|
|
90
|
+
d="M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z"
|
|
91
|
+
fillRule="evenodd"
|
|
92
|
+
/>
|
|
93
|
+
</Logo>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function GeminiLogo(props: ProviderLogoProps) {
|
|
98
|
+
const id = useId();
|
|
99
|
+
// One silhouette painted five times: a blue base and three colour washes that
|
|
100
|
+
// fade out, which is how the upstream mark gets its gradient.
|
|
101
|
+
const fills = [
|
|
102
|
+
"#3186FF",
|
|
103
|
+
`url(#${id}green)`,
|
|
104
|
+
`url(#${id}red)`,
|
|
105
|
+
`url(#${id}yellow)`,
|
|
106
|
+
];
|
|
107
|
+
return (
|
|
108
|
+
<Logo name="gemini" {...props}>
|
|
109
|
+
<defs>
|
|
110
|
+
<linearGradient id={`${id}green`} gradientUnits="userSpaceOnUse" x1="7" x2="11" y1="15.5" y2="12">
|
|
111
|
+
<stop stopColor="#08B962" />
|
|
112
|
+
<stop offset="1" stopColor="#08B962" stopOpacity="0" />
|
|
113
|
+
</linearGradient>
|
|
114
|
+
<linearGradient id={`${id}red`} gradientUnits="userSpaceOnUse" x1="8" x2="11.5" y1="5.5" y2="11">
|
|
115
|
+
<stop stopColor="#F94543" />
|
|
116
|
+
<stop offset="1" stopColor="#F94543" stopOpacity="0" />
|
|
117
|
+
</linearGradient>
|
|
118
|
+
<linearGradient id={`${id}yellow`} gradientUnits="userSpaceOnUse" x1="3.5" x2="17.5" y1="13.5" y2="12">
|
|
119
|
+
<stop stopColor="#FABC12" />
|
|
120
|
+
<stop offset=".46" stopColor="#FABC12" stopOpacity="0" />
|
|
121
|
+
</linearGradient>
|
|
122
|
+
</defs>
|
|
123
|
+
{fills.map((fill) => (
|
|
124
|
+
<path
|
|
125
|
+
key={fill}
|
|
126
|
+
d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"
|
|
127
|
+
fill={fill}
|
|
128
|
+
/>
|
|
129
|
+
))}
|
|
130
|
+
</Logo>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Provider id → logo. An id absent here has no artwork and callers fall back:
|
|
136
|
+
* the tab strip to its generic chat glyph, the pickers to a lettered tile.
|
|
137
|
+
*/
|
|
138
|
+
export const PROVIDER_LOGOS: Record<string, ProviderLogo> = {
|
|
139
|
+
claude: ClaudeLogo,
|
|
140
|
+
codex: CodexLogo,
|
|
141
|
+
cursor: CursorLogo,
|
|
142
|
+
gemini: GeminiLogo,
|
|
143
|
+
};
|
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
import type { ElementType } from "react";
|
|
14
14
|
import type { TabType } from "@/stores/tab-store";
|
|
15
15
|
import { fileIconElement } from "@/lib/file-icons";
|
|
16
|
+
import { PROVIDER_LOGOS } from "@/lib/provider-logos";
|
|
17
|
+
import { viewTypeIcon } from "@/lib/extension-icons";
|
|
18
|
+
import { useExtensionStore } from "@/stores/extension-store";
|
|
16
19
|
|
|
17
20
|
export const TAB_TYPE_ICONS: Record<TabType, LucideIcon> = {
|
|
18
21
|
terminal: Terminal,
|
|
@@ -22,6 +25,7 @@ export const TAB_TYPE_ICONS: Record<TabType, LucideIcon> = {
|
|
|
22
25
|
sqlite: Database,
|
|
23
26
|
postgres: Database,
|
|
24
27
|
"git-diff": FileDiff,
|
|
28
|
+
"branch-review": FileDiff,
|
|
25
29
|
settings: Settings,
|
|
26
30
|
extension: Puzzle,
|
|
27
31
|
"extension-webview": Puzzle,
|
|
@@ -53,11 +57,32 @@ export interface TabIconSubject {
|
|
|
53
57
|
* which tab is which, and that is just as true of the dock header and the mobile
|
|
54
58
|
* tab switcher as it is of the desktop strip. The tab's own metadata is
|
|
55
59
|
* preferred over its title, which a rename or a "(hash)" suffix can have edited.
|
|
60
|
+
*
|
|
61
|
+
* A chat tab is labelled the same way and for the same reason, with the logo of
|
|
62
|
+
* the provider running it. A tab carrying no provider is a chat that has not
|
|
63
|
+
* started yet, and one of those runs Claude — `ChatTab`'s own default — so it is
|
|
64
|
+
* drawn as one rather than as a question mark.
|
|
56
65
|
*/
|
|
57
66
|
export function getTabIcon(tab: TabIconSubject): ElementType {
|
|
58
67
|
if (FILE_TAB_TYPES.has(tab.type)) {
|
|
59
68
|
const path = (tab.metadata?.filePath as string | undefined) || tab.title;
|
|
60
69
|
if (path) return fileIconElement(path);
|
|
61
70
|
}
|
|
71
|
+
if (tab.type === "chat") {
|
|
72
|
+
const logo = PROVIDER_LOGOS[(tab.metadata?.providerId as string | undefined) ?? "claude"];
|
|
73
|
+
if (logo) return logo;
|
|
74
|
+
}
|
|
75
|
+
// A panel an extension opened is labelled with the icon its manifest gave the
|
|
76
|
+
// command that opens it — read here rather than stored on the tab, so a tab
|
|
77
|
+
// that was already open when the extension declared one picks it up too.
|
|
78
|
+
// Falls back to the puzzle piece, which is the honest answer for a panel
|
|
79
|
+
// whose extension has not said what it is.
|
|
80
|
+
if (tab.type === "extension" || tab.type === "extension-webview") {
|
|
81
|
+
const contributed = viewTypeIcon(
|
|
82
|
+
useExtensionStore.getState().contributions,
|
|
83
|
+
tab.metadata?.viewType as string | undefined,
|
|
84
|
+
);
|
|
85
|
+
if (contributed) return contributed;
|
|
86
|
+
}
|
|
62
87
|
return getTabTypeIcon(tab.type);
|
|
63
88
|
}
|
|
@@ -77,8 +77,12 @@ interface FileStore {
|
|
|
77
77
|
error: string | null;
|
|
78
78
|
expandedPaths: Set<string>;
|
|
79
79
|
loadedPaths: Set<string>;
|
|
80
|
-
/**
|
|
81
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Directory loads on their way, by path. The promise is held alongside the
|
|
82
|
+
* controller so a second caller for the same path can wait for the answer
|
|
83
|
+
* already coming rather than asking for it again.
|
|
84
|
+
*/
|
|
85
|
+
inflight: Map<string, InflightLoad>;
|
|
82
86
|
indexStatus: "idle" | "loading" | "ready" | "error";
|
|
83
87
|
selectedFiles: string[];
|
|
84
88
|
inlineAction: InlineAction | null;
|
|
@@ -131,6 +135,13 @@ function queuePrefetchFor(
|
|
|
131
135
|
);
|
|
132
136
|
}
|
|
133
137
|
|
|
138
|
+
interface InflightLoad {
|
|
139
|
+
controller: AbortController;
|
|
140
|
+
promise: Promise<void>;
|
|
141
|
+
/** Whether this load still counts as a prefetch — see the join in `loadChildren`. */
|
|
142
|
+
prefetch: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
134
145
|
export const useFileStore = create<FileStore>((set, get) => ({
|
|
135
146
|
tree: [],
|
|
136
147
|
fileIndex: [],
|
|
@@ -138,7 +149,7 @@ export const useFileStore = create<FileStore>((set, get) => ({
|
|
|
138
149
|
error: null,
|
|
139
150
|
expandedPaths: new Set<string>(),
|
|
140
151
|
loadedPaths: new Set<string>(),
|
|
141
|
-
inflight: new Map<string,
|
|
152
|
+
inflight: new Map<string, InflightLoad>(),
|
|
142
153
|
indexStatus: "idle",
|
|
143
154
|
selectedFiles: [],
|
|
144
155
|
inlineAction: null,
|
|
@@ -174,45 +185,72 @@ export const useFileStore = create<FileStore>((set, get) => ({
|
|
|
174
185
|
|
|
175
186
|
// Idempotent guard — skip if already loaded
|
|
176
187
|
if (state.loadedPaths.has(folderPath)) return;
|
|
177
|
-
// Prefetch never preempts an in-flight user-initiated request
|
|
178
|
-
if (opts?.prefetch && state.inflight.has(folderPath)) return;
|
|
179
188
|
|
|
180
|
-
//
|
|
189
|
+
// A request for this path is already on its way, and it is the same URL
|
|
190
|
+
// carrying the same answer — so wait for it. It used to be aborted and
|
|
191
|
+
// reissued, which meant clicking a folder while its prefetch was still in
|
|
192
|
+
// flight threw that work away and paid the round trip a second time. The
|
|
193
|
+
// prefetch exists to hide exactly that round trip, so the click that
|
|
194
|
+
// benefits most from it was the one that cancelled it.
|
|
181
195
|
const existing = state.inflight.get(folderPath);
|
|
182
|
-
if (existing)
|
|
196
|
+
if (existing) {
|
|
197
|
+
// Joining is not the same as inheriting. A prefetch deliberately does not cascade — one
|
|
198
|
+
// level ahead only — so a *click* that joined one used to end the chain at exactly the
|
|
199
|
+
// folder the user just opened, which is the folder whose children are most likely to be
|
|
200
|
+
// wanted next. The load has not resolved yet and the flag is read when it does, so
|
|
201
|
+
// upgrading it here is enough.
|
|
202
|
+
if (!opts?.prefetch) existing.prefetch = false;
|
|
203
|
+
return existing.promise;
|
|
204
|
+
}
|
|
183
205
|
|
|
184
206
|
const controller = new AbortController();
|
|
207
|
+
// The entry goes into the map before the request starts, so anything asking
|
|
208
|
+
// for this path finds it; `promise` is filled in on the next line, and no
|
|
209
|
+
// other code can run in between.
|
|
210
|
+
const load: InflightLoad = {
|
|
211
|
+
controller,
|
|
212
|
+
promise: undefined as unknown as Promise<void>,
|
|
213
|
+
prefetch: !!opts?.prefetch,
|
|
214
|
+
};
|
|
185
215
|
const inflight = new Map(state.inflight);
|
|
186
|
-
inflight.set(folderPath,
|
|
216
|
+
inflight.set(folderPath, load);
|
|
187
217
|
set({ inflight });
|
|
188
218
|
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
219
|
+
const clearInflight = () => {
|
|
220
|
+
const next = new Map(get().inflight);
|
|
221
|
+
// Only our own entry — a reset may already have replaced the map.
|
|
222
|
+
if (next.get(folderPath) === load) next.delete(folderPath);
|
|
223
|
+
return next;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
load.promise = (async () => {
|
|
227
|
+
try {
|
|
228
|
+
const encodedPath = encodeURIComponent(folderPath);
|
|
229
|
+
const data = await api.get<FileDirEntry[]>(
|
|
230
|
+
`${projectUrl(projectName)}/files/list?path=${encodedPath}`,
|
|
231
|
+
{ signal: controller.signal },
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Check if aborted between request start and completion (defense in depth)
|
|
235
|
+
if (controller.signal.aborted) return;
|
|
236
|
+
|
|
237
|
+
const children = entriesToNodes(data, folderPath);
|
|
238
|
+
const currentState = get();
|
|
239
|
+
const newTree = mergeChildren(currentState.tree, folderPath, children);
|
|
240
|
+
const newLoadedPaths = new Set(currentState.loadedPaths);
|
|
241
|
+
newLoadedPaths.add(folderPath);
|
|
242
|
+
set({ tree: newTree, loadedPaths: newLoadedPaths, inflight: clearInflight() });
|
|
243
|
+
// One level ahead only: prefetched loads don't cascade further. Read off the load
|
|
244
|
+
// rather than off `opts`, because a click that joined this request has already said
|
|
245
|
+
// it is no longer only a prefetch.
|
|
246
|
+
if (!load.prefetch) queuePrefetchFor(get, projectName, children);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
set({ inflight: clearInflight() });
|
|
249
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
195
252
|
|
|
196
|
-
|
|
197
|
-
if (controller.signal.aborted) return;
|
|
198
|
-
|
|
199
|
-
const children = entriesToNodes(data, folderPath);
|
|
200
|
-
const currentState = get();
|
|
201
|
-
const newTree = mergeChildren(currentState.tree, folderPath, children);
|
|
202
|
-
const newLoadedPaths = new Set(currentState.loadedPaths);
|
|
203
|
-
newLoadedPaths.add(folderPath);
|
|
204
|
-
const newInflight = new Map(currentState.inflight);
|
|
205
|
-
newInflight.delete(folderPath);
|
|
206
|
-
set({ tree: newTree, loadedPaths: newLoadedPaths, inflight: newInflight });
|
|
207
|
-
// One level ahead only: prefetched loads don't cascade further
|
|
208
|
-
if (!opts?.prefetch) queuePrefetchFor(get, projectName, children);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
if (err instanceof Error && err.name === "AbortError") return;
|
|
211
|
-
// Remove from inflight on error
|
|
212
|
-
const newInflight = new Map(get().inflight);
|
|
213
|
-
newInflight.delete(folderPath);
|
|
214
|
-
set({ inflight: newInflight });
|
|
215
|
-
}
|
|
253
|
+
return load.promise;
|
|
216
254
|
},
|
|
217
255
|
|
|
218
256
|
loadPathsBatch: async (projectName: string, paths: string[]) => {
|
|
@@ -334,7 +372,7 @@ export const useFileStore = create<FileStore>((set, get) => ({
|
|
|
334
372
|
reset: () => {
|
|
335
373
|
cancelPrefetch();
|
|
336
374
|
// Abort all in-flight requests
|
|
337
|
-
for (const
|
|
375
|
+
for (const load of get().inflight.values()) load.controller.abort();
|
|
338
376
|
set({
|
|
339
377
|
tree: [],
|
|
340
378
|
fileIndex: [],
|
|
@@ -218,6 +218,11 @@ export function deriveTabId(type: TabType, metadata?: Record<string, unknown>):
|
|
|
218
218
|
}
|
|
219
219
|
case "git-diff":
|
|
220
220
|
return `git-diff:${metadata?.filePath ?? "unknown"}`;
|
|
221
|
+
// One review per project: the tab carries its own base/head pickers, so
|
|
222
|
+
// keying by the ref pair would leave a second tab unreachable the moment
|
|
223
|
+
// someone changed them.
|
|
224
|
+
case "branch-review":
|
|
225
|
+
return `branch-review:${metadata?.projectName ?? "unknown"}`;
|
|
221
226
|
case "conflict-editor":
|
|
222
227
|
return `conflict-editor:${metadata?.filePath ?? "unknown"}`;
|
|
223
228
|
case "settings":
|