@hienlh/ppm 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -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/http-api.md +1 -1
- package/bun.lock +16 -1
- package/dist/web/assets/accounts-settings-section-BVr9-FZJ.js +1 -0
- package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-CdDpOwjh.js} +1 -1
- package/dist/web/assets/ai-settings-section-B1hQyjAd.js +1 -0
- package/dist/web/assets/ai-settings-section-X2UOfSaS.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-DLoPK0LD.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-G84Ojzae.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-DQJfjm8E.js +15 -0
- package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
- package/dist/web/assets/code-editor-D04iUPsZ.js +7 -0
- package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-BSccLRGT.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-C4ay676e.js} +2 -2
- 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-DbwEmG7o.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-S6LsALjX.js} +1 -1
- package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
- package/dist/web/assets/diff-viewer-3oBA7vgQ.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-DJmaKVY0.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-BQ0jyAGv.js +2 -0
- package/dist/web/assets/explorer-window-content-jdkWt34J.js +1 -0
- package/dist/web/assets/{extension-manager-section-BhwB6nqr.js → extension-manager-section-DG1guILi.js} +1 -1
- package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-BGSDUbdK.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-Gnyd7han.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-V6Khh15V.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-Bi513qjT.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-DnQwYSmQ.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-DpczdwyV.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-DU68xk06.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-f2HUbGDY.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-DCRsnKg5.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-BpvVEw_Y.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-Bcaf9_bA.js +1 -0
- package/dist/web/assets/markdown-renderer-DGJp8HC3.js +3 -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-Bol3XAO1.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-BXt54VIn.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-DKJUA3Kq.js} +1 -1
- package/dist/web/assets/problems-panel-CFcoUx8h.js +1 -0
- package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-EKr2dqG6.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-B7qRVl8w.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-0WDYsyCA.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-COYDg16H.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-2DqSuS2U.js +1 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-Dm_qMJMH.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-UodhsF7W.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-bG2lYfGv.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-C06bFarv.js +2 -0
- package/dist/web/assets/settings-store-krC6bc79.js +2 -0
- package/dist/web/assets/settings-tab-34RSaASd.js +1 -0
- package/dist/web/assets/settings-window-content-Dr_2lmHH.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-BgRH0cHD.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-DUxGslpa.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-IZuLn7Qh.js +2 -0
- package/dist/web/assets/system-monitor-tab-DiSA490Z.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-CEaFISgj.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-D0TN8Wrq.js} +1 -1
- package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
- package/dist/web/assets/team-member-sheet-DDPNcCjd.js +2 -0
- package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-DCiW_1-E.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-4ypP0NDY.js} +3 -3
- package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
- package/dist/web/assets/tool-cards-Dd6E7Q3k.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-vrfFHuaR.js} +1 -1
- package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CsuZu49X.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-BIVYJBIn.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-BMbufhX7.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-Byj8P_jA.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/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 +134 -1
- package/packages/ext-git-graph/src/webview-html.ts +277 -27
- 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/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/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/git/git-status-panel.tsx +25 -0
- 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/theme-picker.tsx +2 -1
- package/src/web/components/ui/tooltip.tsx +15 -2
- package/src/web/hooks/use-delayed-flag.ts +35 -0
- package/src/web/lib/branch-review-state.ts +175 -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 +11 -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/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-CvL1pWeK.js +0 -3
- 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/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
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { useProjectStore } from "@/stores/project-store";
|
|
24
24
|
import { useTabStore } from "@/stores/tab-store";
|
|
25
25
|
import { useCompareStore } from "@/stores/compare-store";
|
|
26
|
+
import { TreeNodeContextMenu } from "./tree-node-context-menu";
|
|
26
27
|
import { openCompareTab } from "@/lib/open-compare-tab";
|
|
27
28
|
import { toast } from "sonner";
|
|
28
29
|
import { cn, basename } from "@/lib/utils";
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
import { FileActions } from "./file-actions";
|
|
38
39
|
import { TreeRow } from "./tree-node";
|
|
39
40
|
import { InlineTreeInput } from "./inline-tree-input";
|
|
40
|
-
import { flattenVisibleTree, type InputRow } from "./flatten-visible-tree";
|
|
41
|
+
import { flattenVisibleTree, rowKey, type InputRow } from "./flatten-visible-tree";
|
|
41
42
|
import { downloadFile, downloadFolder } from "@/lib/file-download";
|
|
42
43
|
import { api, projectUrl } from "@/lib/api-client";
|
|
43
44
|
import { openExplorer } from "@/components/os-explorer/open-explorer";
|
|
@@ -201,6 +202,128 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
201
202
|
}
|
|
202
203
|
}, [activeProject, selectedFiles, clipboard, setClipboard, pasteFiles]);
|
|
203
204
|
|
|
205
|
+
// Every row is a `memo(TreeRow)` and this is one of its props, so a fresh
|
|
206
|
+
// identity per render defeats the memo for the whole mounted set. The
|
|
207
|
+
// selection is read at call time rather than closed over for the same reason:
|
|
208
|
+
// as a dependency it would rebuild the callback on every click, which is the
|
|
209
|
+
// commonest thing that happens in this panel.
|
|
210
|
+
const handleAction = useCallback(async (action: string, node: FileNode) => {
|
|
211
|
+
const selectedFiles = useFileStore.getState().selectedFiles;
|
|
212
|
+
if (action === "toggle-expand" && node.type === "directory") {
|
|
213
|
+
toggleExpand(activeProject!.name, node.path);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (action === "open-file" && node.type === "file") {
|
|
217
|
+
const ext = node.name.split(".").pop()?.toLowerCase() ?? "";
|
|
218
|
+
const isSqlite = ext === "db" || ext === "sqlite" || ext === "sqlite3";
|
|
219
|
+
openTab({
|
|
220
|
+
type: isSqlite ? "sqlite" : "editor",
|
|
221
|
+
title: node.name,
|
|
222
|
+
metadata: { filePath: node.path, projectName: activeProject!.name },
|
|
223
|
+
projectId: activeProject!.name,
|
|
224
|
+
closable: true,
|
|
225
|
+
});
|
|
226
|
+
onFileOpen?.();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (action === "cut" || action === "copy-file") {
|
|
230
|
+
const paths = selectedFiles.length > 0 && selectedFiles.includes(node.path) ? [...selectedFiles] : [node.path];
|
|
231
|
+
copyToTreeClipboard(paths, action === "cut" ? "cut" : "copy");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (action === "open-in-file-explorer") {
|
|
235
|
+
const target = node.type === "directory" ? node.path : parentDirOfRelative(node.path);
|
|
236
|
+
void openExplorer(absoluteProjectPath(activeProject!.path, target));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (action === "open-in-terminal") {
|
|
240
|
+
const target = node.type === "directory" ? node.path : parentDirOfRelative(node.path);
|
|
241
|
+
openTerminalAt(absoluteProjectPath(activeProject!.path, target), activeProject!.name);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (action === "paste" && node.type === "directory") {
|
|
245
|
+
pasteFiles(node.path);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (action === "copy-path") {
|
|
249
|
+
void copyToClipboard(node.path);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (action === "copy-full-path") {
|
|
253
|
+
const root = activeProject?.path;
|
|
254
|
+
void copyToClipboard(root ? `${root}/${node.path}` : node.path);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (action === "select-for-compare") {
|
|
258
|
+
useCompareStore.getState().setSelection({
|
|
259
|
+
filePath: node.path,
|
|
260
|
+
projectName: activeProject!.name,
|
|
261
|
+
label: node.name,
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (action === "compare-with-selected") {
|
|
266
|
+
const sel = useCompareStore.getState().selection;
|
|
267
|
+
if (!sel) return;
|
|
268
|
+
try {
|
|
269
|
+
await openCompareTab(
|
|
270
|
+
{ path: sel.filePath, dirtyContent: sel.dirtyContent },
|
|
271
|
+
{ path: node.path },
|
|
272
|
+
activeProject!.name,
|
|
273
|
+
);
|
|
274
|
+
useCompareStore.getState().clearSelection();
|
|
275
|
+
} catch (err) {
|
|
276
|
+
const msg = err instanceof Error ? err.message : "Compare failed";
|
|
277
|
+
toast.error(msg);
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (action === "download") {
|
|
282
|
+
if (node.type === "directory") {
|
|
283
|
+
downloadFolder(activeProject!.name, node.path);
|
|
284
|
+
} else {
|
|
285
|
+
downloadFile(activeProject!.name, node.path);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (action === "compare-selected" && selectedFiles.length === 2) {
|
|
290
|
+
const file1 = selectedFiles[0]!;
|
|
291
|
+
const file2 = selectedFiles[1]!;
|
|
292
|
+
const name1 = basename(file1);
|
|
293
|
+
const name2 = basename(file2);
|
|
294
|
+
openTab({
|
|
295
|
+
type: "git-diff",
|
|
296
|
+
title: `Compare ${name1} vs ${name2}`,
|
|
297
|
+
closable: true,
|
|
298
|
+
metadata: {
|
|
299
|
+
projectName: activeProject!.name,
|
|
300
|
+
file1,
|
|
301
|
+
file2,
|
|
302
|
+
},
|
|
303
|
+
projectId: activeProject!.name,
|
|
304
|
+
});
|
|
305
|
+
clearSelection();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (action === "new-file" || action === "new-folder") {
|
|
309
|
+
const parentPath = node.type === "directory" ? node.path : "";
|
|
310
|
+
if (parentPath) setExpanded(parentPath, true);
|
|
311
|
+
setInlineAction({ type: action as "new-file" | "new-folder", parentPath });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (action === "rename") {
|
|
315
|
+
const parentPath = node.path.includes("/")
|
|
316
|
+
? node.path.slice(0, node.path.lastIndexOf("/"))
|
|
317
|
+
: "";
|
|
318
|
+
setInlineAction({ type: "rename", parentPath, existingNode: node });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
setActionState({ action, node });
|
|
322
|
+
}, [
|
|
323
|
+
activeProject, toggleExpand, openTab, onFileOpen,
|
|
324
|
+
copyToTreeClipboard, pasteFiles, clearSelection, setExpanded, setInlineAction,
|
|
325
|
+
]);
|
|
326
|
+
|
|
204
327
|
const { handleTreeKeyDown } = useTreeKeyboardNav({
|
|
205
328
|
tree,
|
|
206
329
|
expandedPaths,
|
|
@@ -303,23 +426,97 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
303
426
|
() => flattenVisibleTree(tree, expandedPaths, inlineAction),
|
|
304
427
|
[tree, expandedPaths, inlineAction],
|
|
305
428
|
);
|
|
429
|
+
// One context menu for the whole tree, resolved from whichever row the gesture
|
|
430
|
+
// landed on — VS Code does the same with a single `tree.onContextMenu`. A menu
|
|
431
|
+
// per row meant every mounted row built this menu's 39 items on every render.
|
|
432
|
+
//
|
|
433
|
+
// The row is read out of the DOM (`data-index` → `rows[i]`) rather than passed
|
|
434
|
+
// in, because by the time the menu opens there is no row component involved.
|
|
435
|
+
// `rows` goes through a ref so the handler itself stays stable: it is on the
|
|
436
|
+
// scroll container, and an unstable one there would be a new listener per render.
|
|
437
|
+
const [menuNode, setMenuNode] = useState<FileNode | null>(null);
|
|
438
|
+
const rowsRef = useRef(rows);
|
|
439
|
+
rowsRef.current = rows;
|
|
440
|
+
const compareSelection = useCompareStore((s) => s.selection);
|
|
441
|
+
const rememberMenuTarget = useCallback(
|
|
442
|
+
(e: React.MouseEvent | React.PointerEvent) => {
|
|
443
|
+
// A finger fires `pointerdown` before `touchstart`, and the long-press timer
|
|
444
|
+
// that opens the sheet is armed 400ms later — so this has landed well before
|
|
445
|
+
// the menu needs it. A mouse opens the menu on `contextmenu` instead; a left
|
|
446
|
+
// click needs no target and would only cost a render.
|
|
447
|
+
if (e.type === "pointerdown" && (e as React.PointerEvent).pointerType === "mouse") return;
|
|
448
|
+
const el = (e.target as HTMLElement | null)?.closest?.("[data-index]");
|
|
449
|
+
const index = el ? Number(el.getAttribute("data-index")) : -1;
|
|
450
|
+
const row = Number.isInteger(index) && index >= 0 ? rowsRef.current[index] : undefined;
|
|
451
|
+
const node = row?.kind === "node" ? row.node : null;
|
|
452
|
+
setMenuNode((prev) => (prev === node ? prev : node));
|
|
453
|
+
},
|
|
454
|
+
[],
|
|
455
|
+
);
|
|
456
|
+
|
|
306
457
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
307
458
|
useDragAutoScroll(scrollRef);
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The keyboard's right-click.
|
|
462
|
+
*
|
|
463
|
+
* Focus lives on the panel container — the rows are virtualized and never take it — so a
|
|
464
|
+
* `ContextMenu` (or Shift+F10) press targets an element that is not inside the radix trigger
|
|
465
|
+
* at all: nothing opened, and there was no pointer position to open it at. Re-aiming the
|
|
466
|
+
* event at the focused row's own element produces exactly what right-clicking that row does,
|
|
467
|
+
* `rememberMenuTarget` included, so there is no second way of resolving the target to keep in
|
|
468
|
+
* step with the first. It is also why `menuNode` is not cleared when the menu closes: every
|
|
469
|
+
* path that opens this menu resolves the row first, and clearing on close would swap the
|
|
470
|
+
* content to the blank-area menu *during* the exit animation, in view.
|
|
471
|
+
*/
|
|
472
|
+
const openMenuForFocusedRow = useCallback((e: React.KeyboardEvent) => {
|
|
473
|
+
if (e.key !== "ContextMenu" && !(e.key === "F10" && e.shiftKey)) return;
|
|
474
|
+
const index = rowsRef.current.findIndex((r) => r.kind === "node" && r.node.path === focusedPath);
|
|
475
|
+
const el = index >= 0 ? scrollRef.current?.querySelector(`[data-index="${index}"]`) : null;
|
|
476
|
+
if (!el) return; // no focused row, or it is outside the rendered range
|
|
477
|
+
e.preventDefault();
|
|
478
|
+
const rect = el.getBoundingClientRect();
|
|
479
|
+
el.dispatchEvent(new MouseEvent("contextmenu", {
|
|
480
|
+
bubbles: true,
|
|
481
|
+
cancelable: true,
|
|
482
|
+
button: 2,
|
|
483
|
+
clientX: Math.round(rect.left + 8),
|
|
484
|
+
clientY: Math.round(rect.top + rect.height / 2),
|
|
485
|
+
}));
|
|
486
|
+
}, [focusedPath]);
|
|
308
487
|
const isMobile = useIsMobile();
|
|
488
|
+
// `directDomUpdates` is what keeps React off the scroll path: without it every
|
|
489
|
+
// scroll event ends in `flushSync(rerender)`, so the whole mounted row set is
|
|
490
|
+
// reconciled *inside* the scroll handler — measured at 92ms of a 137ms long
|
|
491
|
+
// frame on a 6x-throttled CPU. With it on, the virtualizer writes each row's
|
|
492
|
+
// transform itself and React only renders when the visible range changes.
|
|
493
|
+
// Three things below are load-bearing and have to stay in step with it: the row
|
|
494
|
+
// must not set `transform` (the virtualizer owns it), the sizing container must
|
|
495
|
+
// take `containerRef` and not set its own `height`, and `measureElement` must
|
|
496
|
+
// stay — `elementsCache`, which direct updates read to find each node, is
|
|
497
|
+
// populated by that very ref callback.
|
|
309
498
|
const rowVirtualizer = useVirtualizer({
|
|
310
499
|
count: rows.length,
|
|
311
500
|
getScrollElement: () => scrollRef.current,
|
|
312
501
|
estimateSize: () => (isMobile ? 32 : 26),
|
|
502
|
+
// Must match the `key` below. The default is the index, and an index is not
|
|
503
|
+
// an identity: insert a row and every row under it is cached against the
|
|
504
|
+
// slot it used to occupy, so `directDomUpdates` positions none of them.
|
|
505
|
+
// A ResizeObserver can fire for a row React has not unmounted yet, carrying a
|
|
506
|
+
// `data-index` past the end of a list that just shrank — hence the fallback
|
|
507
|
+
// rather than a `!`, since throwing here happens inside the observer.
|
|
508
|
+
getItemKey: (index) => { const r = rows[index]; return r ? rowKey(r) : `#${index}`; },
|
|
313
509
|
overscan: 10,
|
|
314
510
|
paddingStart: 4,
|
|
315
511
|
paddingEnd: 4,
|
|
512
|
+
directDomUpdates: true,
|
|
316
513
|
});
|
|
317
514
|
|
|
318
515
|
// Keep focused row in view even when it's not mounted (offscreen)
|
|
319
516
|
useEffect(() => {
|
|
320
517
|
if (focusedPath == null) return;
|
|
321
518
|
const idx = rows.findIndex(
|
|
322
|
-
(r) => r.kind === "node" &&
|
|
519
|
+
(r) => r.kind === "node" && r.node.path === focusedPath,
|
|
323
520
|
);
|
|
324
521
|
if (idx >= 0) rowVirtualizer.scrollToIndex(idx, { align: "auto" });
|
|
325
522
|
}, [focusedPath]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
@@ -351,118 +548,6 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
351
548
|
}
|
|
352
549
|
}, [activeProject, clearInlineAction]);
|
|
353
550
|
|
|
354
|
-
async function handleAction(action: string, node: FileNode) {
|
|
355
|
-
if (action === "toggle-expand" && node.type === "directory") {
|
|
356
|
-
toggleExpand(activeProject!.name, node.path);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (action === "open-file" && node.type === "file") {
|
|
360
|
-
const ext = node.name.split(".").pop()?.toLowerCase() ?? "";
|
|
361
|
-
const isSqlite = ext === "db" || ext === "sqlite" || ext === "sqlite3";
|
|
362
|
-
openTab({
|
|
363
|
-
type: isSqlite ? "sqlite" : "editor",
|
|
364
|
-
title: node.name,
|
|
365
|
-
metadata: { filePath: node.path, projectName: activeProject!.name },
|
|
366
|
-
projectId: activeProject!.name,
|
|
367
|
-
closable: true,
|
|
368
|
-
});
|
|
369
|
-
onFileOpen?.();
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
if (action === "cut" || action === "copy-file") {
|
|
373
|
-
const paths = selectedFiles.length > 0 && selectedFiles.includes(node.path) ? [...selectedFiles] : [node.path];
|
|
374
|
-
copyToTreeClipboard(paths, action === "cut" ? "cut" : "copy");
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (action === "open-in-file-explorer") {
|
|
378
|
-
const target = node.type === "directory" ? node.path : parentDirOfRelative(node.path);
|
|
379
|
-
void openExplorer(absoluteProjectPath(activeProject!.path, target));
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (action === "open-in-terminal") {
|
|
383
|
-
const target = node.type === "directory" ? node.path : parentDirOfRelative(node.path);
|
|
384
|
-
openTerminalAt(absoluteProjectPath(activeProject!.path, target), activeProject!.name);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (action === "paste" && node.type === "directory") {
|
|
388
|
-
pasteFiles(node.path);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (action === "copy-path") {
|
|
392
|
-
void copyToClipboard(node.path);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
if (action === "copy-full-path") {
|
|
396
|
-
const root = activeProject?.path;
|
|
397
|
-
void copyToClipboard(root ? `${root}/${node.path}` : node.path);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
if (action === "select-for-compare") {
|
|
401
|
-
useCompareStore.getState().setSelection({
|
|
402
|
-
filePath: node.path,
|
|
403
|
-
projectName: activeProject!.name,
|
|
404
|
-
label: node.name,
|
|
405
|
-
});
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
if (action === "compare-with-selected") {
|
|
409
|
-
const sel = useCompareStore.getState().selection;
|
|
410
|
-
if (!sel) return;
|
|
411
|
-
try {
|
|
412
|
-
await openCompareTab(
|
|
413
|
-
{ path: sel.filePath, dirtyContent: sel.dirtyContent },
|
|
414
|
-
{ path: node.path },
|
|
415
|
-
activeProject!.name,
|
|
416
|
-
);
|
|
417
|
-
useCompareStore.getState().clearSelection();
|
|
418
|
-
} catch (err) {
|
|
419
|
-
const msg = err instanceof Error ? err.message : "Compare failed";
|
|
420
|
-
toast.error(msg);
|
|
421
|
-
}
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
if (action === "download") {
|
|
425
|
-
if (node.type === "directory") {
|
|
426
|
-
downloadFolder(activeProject!.name, node.path);
|
|
427
|
-
} else {
|
|
428
|
-
downloadFile(activeProject!.name, node.path);
|
|
429
|
-
}
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
if (action === "compare-selected" && selectedFiles.length === 2) {
|
|
433
|
-
const file1 = selectedFiles[0]!;
|
|
434
|
-
const file2 = selectedFiles[1]!;
|
|
435
|
-
const name1 = basename(file1);
|
|
436
|
-
const name2 = basename(file2);
|
|
437
|
-
openTab({
|
|
438
|
-
type: "git-diff",
|
|
439
|
-
title: `Compare ${name1} vs ${name2}`,
|
|
440
|
-
closable: true,
|
|
441
|
-
metadata: {
|
|
442
|
-
projectName: activeProject!.name,
|
|
443
|
-
file1,
|
|
444
|
-
file2,
|
|
445
|
-
},
|
|
446
|
-
projectId: activeProject!.name,
|
|
447
|
-
});
|
|
448
|
-
clearSelection();
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
if (action === "new-file" || action === "new-folder") {
|
|
452
|
-
const parentPath = node.type === "directory" ? node.path : "";
|
|
453
|
-
if (parentPath) setExpanded(parentPath, true);
|
|
454
|
-
setInlineAction({ type: action as "new-file" | "new-folder", parentPath });
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
if (action === "rename") {
|
|
458
|
-
const parentPath = node.path.includes("/")
|
|
459
|
-
? node.path.slice(0, node.path.lastIndexOf("/"))
|
|
460
|
-
: "";
|
|
461
|
-
setInlineAction({ type: "rename", parentPath, existingNode: node });
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
setActionState({ action, node });
|
|
465
|
-
}
|
|
466
551
|
|
|
467
552
|
if (!activeProject) {
|
|
468
553
|
return (
|
|
@@ -499,7 +584,7 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
499
584
|
ref={treeContainerRef}
|
|
500
585
|
className={cn("flex flex-col h-full outline-none", (isRootDragOver || backgroundDrop.isOver) && "bg-primary/5")}
|
|
501
586
|
tabIndex={0}
|
|
502
|
-
onKeyDown={(e) => { handleClipboardKeyDown(e); handleTreeKeyDown(e); }}
|
|
587
|
+
onKeyDown={(e) => { handleClipboardKeyDown(e); handleTreeKeyDown(e); openMenuForFocusedRow(e); }}
|
|
503
588
|
// Two independent drags share this background: OS files (handleRoot*, unchanged) and
|
|
504
589
|
// a cross-surface entry drag (backgroundDrop, gated on its own MIME) — each ignores
|
|
505
590
|
// the other's kind, so calling both in sequence is safe.
|
|
@@ -529,17 +614,21 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
529
614
|
{/* File tree with blank-area context menu — virtualized flat rows */}
|
|
530
615
|
<ContextMenu>
|
|
531
616
|
<ContextMenuTrigger asChild>
|
|
532
|
-
<div
|
|
533
|
-
|
|
617
|
+
<div
|
|
618
|
+
ref={scrollRef}
|
|
619
|
+
className="flex-1 overflow-y-auto"
|
|
620
|
+
onContextMenuCapture={rememberMenuTarget}
|
|
621
|
+
onPointerDownCapture={rememberMenuTarget}
|
|
622
|
+
>
|
|
623
|
+
<div ref={rowVirtualizer.containerRef} className="relative w-full">
|
|
534
624
|
{rowVirtualizer.getVirtualItems().map((vi) => {
|
|
535
625
|
const row = rows[vi.index]!;
|
|
536
626
|
return (
|
|
537
627
|
<div
|
|
538
|
-
key={row
|
|
628
|
+
key={rowKey(row)}
|
|
539
629
|
data-index={vi.index}
|
|
540
630
|
ref={rowVirtualizer.measureElement}
|
|
541
631
|
className="absolute left-0 top-0 w-full"
|
|
542
|
-
style={{ transform: `translateY(${vi.start}px)` }}
|
|
543
632
|
>
|
|
544
633
|
{row.kind === "input" ? (
|
|
545
634
|
<InlineTreeInput
|
|
@@ -576,21 +665,33 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
576
665
|
</div>
|
|
577
666
|
</div>
|
|
578
667
|
</ContextMenuTrigger>
|
|
579
|
-
|
|
580
|
-
<
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
668
|
+
{menuNode ? (
|
|
669
|
+
<TreeNodeContextMenu
|
|
670
|
+
node={menuNode}
|
|
671
|
+
isDir={menuNode.type === "directory"}
|
|
672
|
+
projectName={activeProject.name}
|
|
673
|
+
selectedFiles={selectedFiles}
|
|
674
|
+
compareSelection={compareSelection}
|
|
675
|
+
clipboard={clipboard}
|
|
676
|
+
onAction={handleAction}
|
|
677
|
+
/>
|
|
678
|
+
) : (
|
|
679
|
+
<ContextMenuContent>
|
|
680
|
+
<ContextMenuItem onClick={() => handleAction("new-file", ROOT_NODE)}>
|
|
681
|
+
<FilePlus className="size-3.5 mr-2" />
|
|
682
|
+
New File
|
|
683
|
+
</ContextMenuItem>
|
|
684
|
+
<ContextMenuItem onClick={() => handleAction("new-folder", ROOT_NODE)}>
|
|
685
|
+
<FolderPlus className="size-3.5 mr-2" />
|
|
686
|
+
New Folder
|
|
687
|
+
</ContextMenuItem>
|
|
688
|
+
<ContextMenuSeparator />
|
|
689
|
+
<ContextMenuItem onClick={reloadTree}>
|
|
690
|
+
<RefreshCw className="size-3.5 mr-2" />
|
|
691
|
+
Refresh
|
|
692
|
+
</ContextMenuItem>
|
|
693
|
+
</ContextMenuContent>
|
|
694
|
+
)}
|
|
594
695
|
</ContextMenu>
|
|
595
696
|
|
|
596
697
|
{actionState?.action === "delete" && (
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single source of truth for the explorer's visible-row computation.
|
|
3
3
|
* Flattens the lazy tree into ordered rows respecting expand state,
|
|
4
|
-
* dir-first sorting,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* dir-first sorting, and inline create/rename input rows. Consumed by the
|
|
5
|
+
* virtualized renderer, keyboard navigation, and range selection.
|
|
6
|
+
*
|
|
7
|
+
* Every directory gets its own row. Single-child chains used to be joined into
|
|
8
|
+
* one `a/b/c` row (VS Code's compact folders) — but only *after* the child was
|
|
9
|
+
* expanded, because the join required both paths to be in `expandedPaths`. So
|
|
10
|
+
* the gesture that should have revealed a folder instead made the row you just
|
|
11
|
+
* clicked vanish into its parent, and depth stopped matching the hierarchy.
|
|
12
|
+
* Expanding is expanding: one row per directory, always.
|
|
7
13
|
*/
|
|
8
14
|
import type { FileNode, InlineAction } from "@/stores/file-store";
|
|
9
15
|
|
|
10
16
|
export interface NodeRow {
|
|
11
17
|
kind: "node";
|
|
12
|
-
/**
|
|
18
|
+
/** Row identity, drag source, context menu target */
|
|
13
19
|
node: FileNode;
|
|
14
|
-
/** Terminal node of a compacted single-child dir chain (equals node when not compacted) */
|
|
15
|
-
effectiveNode: FileNode;
|
|
16
|
-
/** Display label, e.g. "a/b/c" for compacted chains */
|
|
17
|
-
displayName: string;
|
|
18
20
|
depth: number;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -22,12 +24,28 @@ export interface InputRow {
|
|
|
22
24
|
kind: "input";
|
|
23
25
|
inline: InlineAction;
|
|
24
26
|
depth: number;
|
|
25
|
-
/** Directory the created entry lands in
|
|
27
|
+
/** Directory the created entry lands in */
|
|
26
28
|
targetPath: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export type FlatRow = NodeRow | InputRow;
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* A row's identity, independent of where it currently sits in the list.
|
|
35
|
+
*
|
|
36
|
+
* This is React's `key` *and* the virtualizer's `getItemKey`, and the two have
|
|
37
|
+
* to be the same string. The virtualizer caches the DOM node it positions under
|
|
38
|
+
* its own key, and it only learns of a node through the `measureElement` ref —
|
|
39
|
+
* which React does not call again when a row merely changes index. So with the
|
|
40
|
+
* default key (the index), expanding a folder left every row below it registered
|
|
41
|
+
* under the index it used to have: the inserted rows overwrote those entries and
|
|
42
|
+
* the shifted ones were positioned by nothing at all, landing on top of each
|
|
43
|
+
* other at their old offsets.
|
|
44
|
+
*/
|
|
45
|
+
export function rowKey(row: FlatRow): string {
|
|
46
|
+
return row.kind === "node" ? row.node.path : `input:${row.targetPath}:${row.inline.type}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
function sortNodes(nodes: FileNode[]): FileNode[] {
|
|
32
50
|
return [...nodes].sort((a, b) => {
|
|
33
51
|
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
@@ -35,24 +53,6 @@ function sortNodes(nodes: FileNode[]): FileNode[] {
|
|
|
35
53
|
});
|
|
36
54
|
}
|
|
37
55
|
|
|
38
|
-
/** Follow expanded single-child dir chains; returns terminal node + joined label */
|
|
39
|
-
function compactChain(node: FileNode, expandedPaths: Set<string>): { effective: FileNode; displayName: string } {
|
|
40
|
-
let effective = node;
|
|
41
|
-
let displayName = node.name;
|
|
42
|
-
if (node.type === "directory" && expandedPaths.has(node.path) && node.children) {
|
|
43
|
-
while (
|
|
44
|
-
effective.children &&
|
|
45
|
-
effective.children.length === 1 &&
|
|
46
|
-
effective.children[0]!.type === "directory" &&
|
|
47
|
-
expandedPaths.has(effective.children[0]!.path)
|
|
48
|
-
) {
|
|
49
|
-
effective = effective.children[0]!;
|
|
50
|
-
displayName += `/${effective.name}`;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return { effective, displayName };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
56
|
export function flattenVisibleTree(
|
|
57
57
|
tree: FileNode[],
|
|
58
58
|
expandedPaths: Set<string>,
|
|
@@ -69,24 +69,19 @@ export function flattenVisibleTree(
|
|
|
69
69
|
|
|
70
70
|
function walk(nodes: FileNode[], depth: number) {
|
|
71
71
|
for (const n of sortNodes(nodes)) {
|
|
72
|
-
const { effective, displayName } = compactChain(n, expandedPaths);
|
|
73
|
-
|
|
74
72
|
if (renamePath != null && renamePath === n.path) {
|
|
75
73
|
rows.push({ kind: "input", inline: inlineAction!, depth, targetPath: n.path });
|
|
76
74
|
} else {
|
|
77
|
-
rows.push({ kind: "node", node: n,
|
|
75
|
+
rows.push({ kind: "node", node: n, depth });
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
const expanded =
|
|
78
|
+
const expanded = n.type === "directory" && expandedPaths.has(n.path);
|
|
81
79
|
// Create input pinned under its parent dir, before children
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
(inlineAction!.parentPath === n.path || inlineAction!.parentPath === effective.path)
|
|
85
|
-
) {
|
|
86
|
-
rows.push({ kind: "input", inline: inlineAction!, depth: depth + 1, targetPath: effective.path });
|
|
80
|
+
if (isCreate && expanded && inlineAction!.parentPath === n.path) {
|
|
81
|
+
rows.push({ kind: "input", inline: inlineAction!, depth: depth + 1, targetPath: n.path });
|
|
87
82
|
}
|
|
88
|
-
if (expanded &&
|
|
89
|
-
walk(
|
|
83
|
+
if (expanded && n.children) {
|
|
84
|
+
walk(n.children, depth + 1);
|
|
90
85
|
}
|
|
91
86
|
}
|
|
92
87
|
}
|
|
@@ -94,9 +89,9 @@ export function flattenVisibleTree(
|
|
|
94
89
|
return rows;
|
|
95
90
|
}
|
|
96
91
|
|
|
97
|
-
/** Flat visible
|
|
92
|
+
/** Flat visible node list (keyboard nav, range selection) */
|
|
98
93
|
export function visibleNodesOf(tree: FileNode[], expandedPaths: Set<string>): FileNode[] {
|
|
99
94
|
return flattenVisibleTree(tree, expandedPaths)
|
|
100
95
|
.filter((r): r is NodeRow => r.kind === "node")
|
|
101
|
-
.map((r) => r.
|
|
96
|
+
.map((r) => r.node);
|
|
102
97
|
}
|