@hienlh/ppm 0.9.85 → 0.9.87
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/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
- package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
- package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
- package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
- package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
- package/CHANGELOG.md +24 -0
- package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
- package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
- package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
- package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
- package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
- package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
- package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
- package/dist/web/assets/channel-wumTB1if.js +1 -0
- package/dist/web/assets/chat-tab-R4gKsnxD.js +10 -0
- package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
- package/dist/web/assets/clone--z5KLAuR.js +1 -0
- package/dist/web/assets/code-editor-Br0vzTOy.js +8 -0
- package/dist/web/assets/columns-2-IeETSfON.js +1 -0
- package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
- package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-BZRICDP0.js} +2 -2
- package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
- package/dist/web/assets/database-CgTomMxt.js +1 -0
- package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-DaUoQ-oR.js} +2 -2
- package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
- package/dist/web/assets/diff-viewer-BzvK3gAE.js +4 -0
- package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
- package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
- package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
- package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
- package/dist/web/assets/index-CKsEzQ4f.js +26 -0
- package/dist/web/assets/index-Chf0otez.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
- package/dist/web/assets/input-BHj0veau.js +45 -0
- package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
- package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
- package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
- package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
- package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-DSYnGywb.js} +6 -6
- package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
- package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
- package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
- package/dist/web/assets/port-forwarding-tab-vmqDKmk2.js +1 -0
- package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-0lIAosrr.js} +3 -3
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
- package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
- package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
- package/dist/web/assets/settings-tab-CMnv1fce.js +1 -0
- package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-Bc2hAwqT.js} +1 -1
- package/dist/web/assets/sqlite-viewer-B60MS2Dy.js +1 -0
- package/dist/web/assets/square-vBdqj0bF.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
- package/dist/web/assets/table-Bi27fEaN.js +1 -0
- package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-CCJoLstH.js} +2 -2
- package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
- package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
- package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
- package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-BJK48EmK.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
- package/dist/web/assets/x-Dw3TjeY_.js +1 -0
- package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
- package/dist/web/index.html +18 -22
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +169 -13
- package/docs/extension-development-guide.md +98 -1
- package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
- package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
- package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
- package/docs/journals/260414-2001-bundled-extensions.md +219 -0
- package/docs/project-changelog.md +123 -21
- package/docs/project-roadmap.md +4 -2
- package/docs/system-architecture.md +77 -6
- package/package.json +1 -1
- package/packages/ext-git-graph/package.json +30 -0
- package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
- package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
- package/packages/ext-git-graph/src/extension.ts +921 -0
- package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
- package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
- package/packages/ext-git-graph/src/types.ts +192 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
- package/packages/ext-git-graph/src/webview-html.ts +2417 -0
- package/packages/vscode-compat/src/index.ts +4 -0
- package/packages/vscode-compat/src/process.ts +25 -0
- package/packages/vscode-compat/src/window.ts +10 -0
- package/src/cli/commands/ext-cmd.ts +3 -1
- package/src/server/ws/extensions.ts +34 -4
- package/src/services/contribution-registry.ts +14 -1
- package/src/services/extension-host-worker.ts +12 -3
- package/src/services/extension-manifest.ts +18 -1
- package/src/services/extension-rpc-handlers.ts +68 -2
- package/src/services/extension.service.ts +63 -9
- package/src/types/extension-messages.ts +3 -1
- package/src/types/extension.ts +8 -0
- package/src/web/components/editor/code-editor.tsx +16 -4
- package/src/web/components/editor/conflict-editor.tsx +368 -0
- package/src/web/components/extensions/extension-webview.tsx +153 -12
- package/src/web/components/layout/command-palette.tsx +41 -17
- package/src/web/components/layout/editor-panel.tsx +16 -4
- package/src/web/components/layout/mobile-nav.tsx +6 -5
- package/src/web/components/layout/tab-bar.tsx +3 -3
- package/src/web/components/layout/tab-content.tsx +17 -5
- package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
- package/src/web/hooks/use-extension-ws.ts +30 -4
- package/src/web/hooks/use-global-keybindings.ts +24 -2
- package/src/web/hooks/use-url-sync.ts +8 -3
- package/src/web/stores/extension-store.ts +8 -0
- package/src/web/stores/keybindings-store.ts +2 -3
- package/src/web/stores/panel-store.ts +2 -2
- package/src/web/stores/panel-utils.ts +6 -2
- package/src/web/stores/tab-store.ts +3 -2
- package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
- package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
- package/dist/web/assets/channel-Cgy1thYT.js +0 -1
- package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
- package/dist/web/assets/check-ePA3ZvK4.js +0 -1
- package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
- package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
- package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
- package/dist/web/assets/clone--C7Tby8z.js +0 -1
- package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
- package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
- package/dist/web/assets/database-D1ToEV9d.js +0 -1
- package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
- package/dist/web/assets/dist-KUoHa6tg.js +0 -1
- package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
- package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
- package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
- package/dist/web/assets/index-CkaCzNgO.css +0 -2
- package/dist/web/assets/index-Ic5uTu20.js +0 -26
- package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
- package/dist/web/assets/input-CcbTF6ih.js +0 -45
- package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
- package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
- package/dist/web/assets/plus-Iso5r9vD.js +0 -1
- package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
- package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
- package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
- package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
- package/dist/web/assets/square-pfn_LYYy.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
- package/dist/web/assets/table-CHv2x_qg.js +0 -1
- package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
- package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
- package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
- package/dist/web/assets/x-BXecj-16.js +0 -1
- package/src/web/components/git/git-graph-branch-label.tsx +0 -124
- package/src/web/components/git/git-graph-constants.ts +0 -185
- package/src/web/components/git/git-graph-detail.tsx +0 -107
- package/src/web/components/git/git-graph-dialog.tsx +0 -72
- package/src/web/components/git/git-graph-row.tsx +0 -167
- package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
- package/src/web/components/git/git-graph-svg.tsx +0 -54
- package/src/web/components/git/git-graph-toolbar.tsx +0 -195
- package/src/web/components/git/git-graph.tsx +0 -193
- package/src/web/components/git/use-column-resize.ts +0 -33
- package/src/web/components/git/use-git-graph.ts +0 -201
- /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
- /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
- /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
- /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
- /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
- /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
- /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
- /package/dist/web/assets/{lib-CfWBrYll.js → lib-DSLzfeW0.js} +0 -0
- /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
- /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
- /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
- /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
- /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
- /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
- /package/dist/web/assets/{utils-CSCvNZxE.js → utils-DX8jb5qv.js} +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useCallback } from "react";
|
|
2
|
+
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
3
|
+
import type * as MonacoType from "monaco-editor";
|
|
4
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
5
|
+
import { useSettingsStore } from "@/stores/settings-store";
|
|
6
|
+
import { useMonacoTheme } from "@/lib/use-monaco-theme";
|
|
7
|
+
import { Loader2 } from "lucide-react";
|
|
8
|
+
|
|
9
|
+
function getMonacoLanguage(filename: string): string {
|
|
10
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
11
|
+
const map: Record<string, string> = {
|
|
12
|
+
js: "javascript", jsx: "javascript",
|
|
13
|
+
ts: "typescript", tsx: "typescript",
|
|
14
|
+
py: "python", html: "html",
|
|
15
|
+
css: "css", scss: "scss",
|
|
16
|
+
json: "json", md: "markdown", mdx: "markdown",
|
|
17
|
+
yaml: "yaml", yml: "yaml",
|
|
18
|
+
sh: "shell", bash: "shell",
|
|
19
|
+
go: "go", rs: "rust", java: "java",
|
|
20
|
+
rb: "ruby", php: "php", swift: "swift",
|
|
21
|
+
sql: "sql", xml: "xml", toml: "toml",
|
|
22
|
+
};
|
|
23
|
+
return map[ext] ?? "plaintext";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ConflictRegion {
|
|
27
|
+
id: number;
|
|
28
|
+
startLine: number; // 1-indexed, line of <<<<<<< marker
|
|
29
|
+
separatorLine: number; // line of =======
|
|
30
|
+
endLine: number; // line of >>>>>>> marker
|
|
31
|
+
currentContent: string;
|
|
32
|
+
incomingContent: string;
|
|
33
|
+
currentLabel: string;
|
|
34
|
+
incomingLabel: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseConflicts(content: string): ConflictRegion[] {
|
|
38
|
+
const lines = content.split("\n");
|
|
39
|
+
const regions: ConflictRegion[] = [];
|
|
40
|
+
let i = 0;
|
|
41
|
+
let id = 0;
|
|
42
|
+
|
|
43
|
+
while (i < lines.length) {
|
|
44
|
+
const line = lines[i]!;
|
|
45
|
+
if (line.startsWith("<<<<<<<")) {
|
|
46
|
+
const startLine = i;
|
|
47
|
+
const currentLabel = line.substring(7).trim();
|
|
48
|
+
const currentLines: string[] = [];
|
|
49
|
+
i++;
|
|
50
|
+
|
|
51
|
+
while (i < lines.length && !lines[i]!.startsWith("=======")) {
|
|
52
|
+
currentLines.push(lines[i]!);
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
if (i >= lines.length) break;
|
|
56
|
+
|
|
57
|
+
const separatorLine = i;
|
|
58
|
+
const incomingLines: string[] = [];
|
|
59
|
+
i++;
|
|
60
|
+
|
|
61
|
+
while (i < lines.length && !lines[i]!.startsWith(">>>>>>>")) {
|
|
62
|
+
incomingLines.push(lines[i]!);
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
if (i >= lines.length) break;
|
|
66
|
+
|
|
67
|
+
const incomingLabel = lines[i]!.substring(7).trim();
|
|
68
|
+
|
|
69
|
+
regions.push({
|
|
70
|
+
id: id++,
|
|
71
|
+
startLine: startLine + 1,
|
|
72
|
+
separatorLine: separatorLine + 1,
|
|
73
|
+
endLine: i + 1,
|
|
74
|
+
currentContent: currentLines.join("\n"),
|
|
75
|
+
incomingContent: incomingLines.join("\n"),
|
|
76
|
+
currentLabel,
|
|
77
|
+
incomingLabel,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
return regions;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface ConflictEditorProps {
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
tabId?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function ConflictEditor({ metadata }: ConflictEditorProps) {
|
|
91
|
+
const filePath = metadata?.filePath as string | undefined;
|
|
92
|
+
const projectName = metadata?.projectName as string | undefined;
|
|
93
|
+
|
|
94
|
+
const [content, setContent] = useState<string | null>(null);
|
|
95
|
+
const [loading, setLoading] = useState(true);
|
|
96
|
+
const [error, setError] = useState<string | null>(null);
|
|
97
|
+
const [conflictCount, setConflictCount] = useState(0);
|
|
98
|
+
const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);
|
|
99
|
+
const monacoRef = useRef<typeof MonacoType | null>(null);
|
|
100
|
+
const widgetsRef = useRef<MonacoType.editor.IContentWidget[]>([]);
|
|
101
|
+
const decorationsRef = useRef<MonacoType.editor.IEditorDecorationsCollection | null>(null);
|
|
102
|
+
|
|
103
|
+
const { wordWrap } = useSettingsStore();
|
|
104
|
+
const monacoTheme = useMonacoTheme();
|
|
105
|
+
|
|
106
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
107
|
+
const [containerHeight, setContainerHeight] = useState<number | undefined>();
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const el = containerRef.current;
|
|
111
|
+
if (!el) return;
|
|
112
|
+
const ro = new ResizeObserver(([entry]) => {
|
|
113
|
+
if (entry) setContainerHeight(Math.floor(entry.contentRect.height));
|
|
114
|
+
});
|
|
115
|
+
ro.observe(el);
|
|
116
|
+
return () => ro.disconnect();
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
// Load file content
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!filePath || !projectName) return;
|
|
122
|
+
setLoading(true);
|
|
123
|
+
api
|
|
124
|
+
.get<{ content: string }>(`${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`)
|
|
125
|
+
.then((data) => {
|
|
126
|
+
setContent(data.content);
|
|
127
|
+
setLoading(false);
|
|
128
|
+
})
|
|
129
|
+
.catch((e: Error) => {
|
|
130
|
+
setError(e.message || "Failed to load file");
|
|
131
|
+
setLoading(false);
|
|
132
|
+
});
|
|
133
|
+
}, [filePath, projectName]);
|
|
134
|
+
|
|
135
|
+
const refreshConflicts = useCallback(() => {
|
|
136
|
+
const editor = editorRef.current;
|
|
137
|
+
const monaco = monacoRef.current;
|
|
138
|
+
if (!editor || !monaco) return;
|
|
139
|
+
|
|
140
|
+
const value = editor.getModel()?.getValue() || "";
|
|
141
|
+
const regions = parseConflicts(value);
|
|
142
|
+
setConflictCount(regions.length);
|
|
143
|
+
|
|
144
|
+
// Clear old widgets
|
|
145
|
+
for (const w of widgetsRef.current) {
|
|
146
|
+
editor.removeContentWidget(w);
|
|
147
|
+
}
|
|
148
|
+
widgetsRef.current = [];
|
|
149
|
+
|
|
150
|
+
// Clear old decorations
|
|
151
|
+
if (decorationsRef.current) {
|
|
152
|
+
decorationsRef.current.clear();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (regions.length === 0) return;
|
|
156
|
+
|
|
157
|
+
// Apply decorations
|
|
158
|
+
const decos: MonacoType.editor.IModelDeltaDecoration[] = [];
|
|
159
|
+
for (const region of regions) {
|
|
160
|
+
// Marker lines
|
|
161
|
+
decos.push({
|
|
162
|
+
range: new monaco.Range(region.startLine, 1, region.startLine, 1),
|
|
163
|
+
options: { isWholeLine: true, className: "conflict-marker-line", glyphMarginClassName: "conflict-glyph-current" },
|
|
164
|
+
});
|
|
165
|
+
decos.push({
|
|
166
|
+
range: new monaco.Range(region.separatorLine, 1, region.separatorLine, 1),
|
|
167
|
+
options: { isWholeLine: true, className: "conflict-marker-line" },
|
|
168
|
+
});
|
|
169
|
+
decos.push({
|
|
170
|
+
range: new monaco.Range(region.endLine, 1, region.endLine, 1),
|
|
171
|
+
options: { isWholeLine: true, className: "conflict-marker-line", glyphMarginClassName: "conflict-glyph-incoming" },
|
|
172
|
+
});
|
|
173
|
+
// Current content (green)
|
|
174
|
+
if (region.separatorLine - region.startLine > 1) {
|
|
175
|
+
decos.push({
|
|
176
|
+
range: new monaco.Range(region.startLine + 1, 1, region.separatorLine - 1, 1),
|
|
177
|
+
options: { isWholeLine: true, className: "conflict-current-content" },
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Incoming content (blue)
|
|
181
|
+
if (region.endLine - region.separatorLine > 1) {
|
|
182
|
+
decos.push({
|
|
183
|
+
range: new monaco.Range(region.separatorLine + 1, 1, region.endLine - 1, 1),
|
|
184
|
+
options: { isWholeLine: true, className: "conflict-incoming-content" },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
decorationsRef.current = editor.createDecorationsCollection(decos);
|
|
190
|
+
|
|
191
|
+
// Add accept widgets above each conflict
|
|
192
|
+
for (const region of regions) {
|
|
193
|
+
const widgetId = `conflict-widget-${region.id}`;
|
|
194
|
+
|
|
195
|
+
const domNode = document.createElement("div");
|
|
196
|
+
domNode.className = "conflict-actions";
|
|
197
|
+
domNode.innerHTML =
|
|
198
|
+
`<span class="conflict-label">Current Change (${escHtml(region.currentLabel || "HEAD")})</span>` +
|
|
199
|
+
`<button class="conflict-btn conflict-btn-current" data-action="current">Accept Current</button>` +
|
|
200
|
+
`<button class="conflict-btn conflict-btn-incoming" data-action="incoming">Accept Incoming</button>` +
|
|
201
|
+
`<button class="conflict-btn conflict-btn-both" data-action="both">Accept Both</button>`;
|
|
202
|
+
|
|
203
|
+
domNode.addEventListener("click", (e) => {
|
|
204
|
+
const btn = (e.target as HTMLElement).closest("[data-action]");
|
|
205
|
+
if (!btn) return;
|
|
206
|
+
const action = btn.getAttribute("data-action") as "current" | "incoming" | "both";
|
|
207
|
+
acceptConflict(region.id, action);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const widget: MonacoType.editor.IContentWidget = {
|
|
211
|
+
getId: () => widgetId,
|
|
212
|
+
getDomNode: () => domNode,
|
|
213
|
+
getPosition: () => ({
|
|
214
|
+
position: { lineNumber: region.startLine, column: 1 },
|
|
215
|
+
preference: [monaco.editor.ContentWidgetPositionPreference.ABOVE],
|
|
216
|
+
}),
|
|
217
|
+
};
|
|
218
|
+
editor.addContentWidget(widget);
|
|
219
|
+
widgetsRef.current.push(widget);
|
|
220
|
+
}
|
|
221
|
+
}, []);
|
|
222
|
+
|
|
223
|
+
const acceptConflict = useCallback(
|
|
224
|
+
(regionId: number, action: "current" | "incoming" | "both") => {
|
|
225
|
+
const editor = editorRef.current;
|
|
226
|
+
const monaco = monacoRef.current;
|
|
227
|
+
if (!editor || !monaco) return;
|
|
228
|
+
|
|
229
|
+
const model = editor.getModel();
|
|
230
|
+
if (!model) return;
|
|
231
|
+
|
|
232
|
+
const value = model.getValue();
|
|
233
|
+
const regions = parseConflicts(value);
|
|
234
|
+
const region = regions.find((r) => r.id === regionId);
|
|
235
|
+
if (!region) return;
|
|
236
|
+
|
|
237
|
+
let replacement: string;
|
|
238
|
+
switch (action) {
|
|
239
|
+
case "current":
|
|
240
|
+
replacement = region.currentContent;
|
|
241
|
+
break;
|
|
242
|
+
case "incoming":
|
|
243
|
+
replacement = region.incomingContent;
|
|
244
|
+
break;
|
|
245
|
+
case "both":
|
|
246
|
+
replacement = region.currentContent + "\n" + region.incomingContent;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const range = new monaco.Range(region.startLine, 1, region.endLine + 1, 1);
|
|
251
|
+
model.pushEditOperations(
|
|
252
|
+
[],
|
|
253
|
+
[{ range, text: replacement + "\n" }],
|
|
254
|
+
() => null,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Save and refresh
|
|
258
|
+
saveFile(model.getValue());
|
|
259
|
+
// Small delay to let the model update before refreshing decorations
|
|
260
|
+
setTimeout(() => refreshConflicts(), 50);
|
|
261
|
+
},
|
|
262
|
+
[refreshConflicts],
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const saveFile = useCallback(
|
|
266
|
+
async (newContent: string) => {
|
|
267
|
+
if (!filePath || !projectName) return;
|
|
268
|
+
try {
|
|
269
|
+
await api.put<{ written: boolean }>(`${projectUrl(projectName)}/files/write`, {
|
|
270
|
+
path: filePath,
|
|
271
|
+
content: newContent,
|
|
272
|
+
});
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error("[conflict-editor] save failed:", e);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
[filePath, projectName],
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const handleMount: OnMount = (editor, monaco) => {
|
|
281
|
+
editorRef.current = editor;
|
|
282
|
+
monacoRef.current = monaco;
|
|
283
|
+
|
|
284
|
+
// Inject conflict editor styles (idempotent)
|
|
285
|
+
const doc = editor.getDomNode()?.ownerDocument ?? document;
|
|
286
|
+
if (!doc.getElementById("conflict-editor-styles")) {
|
|
287
|
+
const styleEl = doc.createElement("style");
|
|
288
|
+
styleEl.id = "conflict-editor-styles";
|
|
289
|
+
styleEl.textContent = `
|
|
290
|
+
.conflict-current-content { background: rgba(34, 197, 94, 0.1) !important; }
|
|
291
|
+
.conflict-incoming-content { background: rgba(59, 130, 246, 0.1) !important; }
|
|
292
|
+
.conflict-marker-line { background: rgba(100, 100, 100, 0.15) !important; font-style: italic; }
|
|
293
|
+
.conflict-glyph-current { background: #22c55e !important; }
|
|
294
|
+
.conflict-glyph-incoming { background: #3b82f6 !important; }
|
|
295
|
+
.conflict-actions { display: flex; gap: 8px; align-items: center; padding: 2px 0; font-size: 12px; font-family: system-ui; }
|
|
296
|
+
.conflict-label { color: #22c55e; font-weight: 600; margin-right: 8px; }
|
|
297
|
+
.conflict-btn { padding: 1px 8px; border-radius: 3px; border: none; cursor: pointer; font-size: 11px; opacity: 0.9; }
|
|
298
|
+
.conflict-btn:hover { opacity: 1; }
|
|
299
|
+
.conflict-btn-current { color: #22c55e; background: rgba(34, 197, 94, 0.15); }
|
|
300
|
+
.conflict-btn-incoming { color: #3b82f6; background: rgba(59, 130, 246, 0.15); }
|
|
301
|
+
.conflict-btn-both { color: #a855f7; background: rgba(168, 85, 247, 0.15); }
|
|
302
|
+
`;
|
|
303
|
+
doc.head?.appendChild(styleEl);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
refreshConflicts();
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const fileName = filePath?.split(/[\\/]/).pop() ?? "unknown";
|
|
310
|
+
const language = getMonacoLanguage(fileName);
|
|
311
|
+
|
|
312
|
+
if (loading) {
|
|
313
|
+
return (
|
|
314
|
+
<div className="flex items-center justify-center h-full">
|
|
315
|
+
<Loader2 className="size-6 animate-spin text-primary" />
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (error) {
|
|
321
|
+
return (
|
|
322
|
+
<div className="flex items-center justify-center h-full text-destructive">
|
|
323
|
+
{error}
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<div className="h-full w-full flex flex-col">
|
|
330
|
+
<div className="flex items-center gap-2 px-3 py-1.5 text-xs border-b border-border bg-muted/50 flex-shrink-0">
|
|
331
|
+
<span className="font-medium">{fileName}</span>
|
|
332
|
+
<span className="text-muted-foreground">—</span>
|
|
333
|
+
{conflictCount > 0 ? (
|
|
334
|
+
<span className="text-destructive font-medium">
|
|
335
|
+
{conflictCount} conflict{conflictCount !== 1 ? "s" : ""} remaining
|
|
336
|
+
</span>
|
|
337
|
+
) : (
|
|
338
|
+
<span className="text-green-500 font-medium">All conflicts resolved</span>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
<div ref={containerRef} className="flex-1 min-h-0">
|
|
342
|
+
{content !== null && containerHeight && (
|
|
343
|
+
<Editor
|
|
344
|
+
height={containerHeight}
|
|
345
|
+
language={language}
|
|
346
|
+
value={content}
|
|
347
|
+
onMount={handleMount}
|
|
348
|
+
theme={monacoTheme}
|
|
349
|
+
options={{
|
|
350
|
+
fontSize: 13,
|
|
351
|
+
fontFamily: "Menlo, Monaco, Consolas, monospace",
|
|
352
|
+
wordWrap: wordWrap ? "on" : "off",
|
|
353
|
+
glyphMargin: true,
|
|
354
|
+
readOnly: false,
|
|
355
|
+
automaticLayout: true,
|
|
356
|
+
scrollBeyondLastLine: false,
|
|
357
|
+
minimap: { enabled: false },
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function escHtml(s: string): string {
|
|
367
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
368
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useRef, useEffect, useCallback } from "react";
|
|
1
|
+
import { useRef, useEffect, useState, useCallback } from "react";
|
|
2
2
|
import { useExtensionStore } from "@/stores/extension-store";
|
|
3
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
3
5
|
|
|
4
6
|
/** Inject acquireVsCodeApi() shim so extension webviews can postMessage to parent */
|
|
5
7
|
const VSCODE_API_SHIM = `<script>
|
|
@@ -22,49 +24,188 @@ interface ExtensionWebviewProps {
|
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* iframe-based webview container for extension-contributed webview panels.
|
|
25
|
-
*
|
|
27
|
+
* Matches panel by panelId (direct) or viewType (reload recovery).
|
|
26
28
|
*/
|
|
27
29
|
export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
28
30
|
const panelId = metadata?.panelId as string | undefined;
|
|
29
|
-
const
|
|
31
|
+
const viewType = metadata?.viewType as string | undefined;
|
|
32
|
+
const currentProject = useTabStore((s) => s.currentProject);
|
|
33
|
+
const projectName = (metadata?.projectName as string | undefined) || currentProject || undefined;
|
|
34
|
+
const [timedOut, setTimedOut] = useState(false);
|
|
35
|
+
|
|
36
|
+
// Match panel: prefer panelId (exact), fallback to viewType match (reload recovery)
|
|
37
|
+
const panel = useExtensionStore((s) => {
|
|
38
|
+
if (panelId && s.webviewPanels[panelId]) return s.webviewPanels[panelId];
|
|
39
|
+
if (viewType) {
|
|
40
|
+
// Find panel whose viewType matches (with or without .view suffix)
|
|
41
|
+
const fullViewType = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
42
|
+
return Object.values(s.webviewPanels).find(
|
|
43
|
+
(p) => p.viewType === viewType || p.viewType === fullViewType,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const resolvedPanelId = panel?.id ?? panelId;
|
|
30
50
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
31
51
|
|
|
32
52
|
// Inject acquireVsCodeApi shim + write HTML into iframe via srcdoc
|
|
33
53
|
const rawHtml = panel?.html ?? "";
|
|
34
54
|
const html = injectVscodeApiShim(rawHtml);
|
|
35
55
|
|
|
56
|
+
// On reload: resolve project path, then dispatch command with retry
|
|
57
|
+
// Retry needed because WS connection may not be ready on first attempt
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (panel || !viewType) return;
|
|
60
|
+
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
let resolvedArgs: unknown[] | null = null;
|
|
63
|
+
|
|
64
|
+
async function resolveArgs(): Promise<unknown[]> {
|
|
65
|
+
if (resolvedArgs) return resolvedArgs;
|
|
66
|
+
if (!projectName) return [];
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch("/api/projects");
|
|
69
|
+
const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };
|
|
70
|
+
const match = json.data?.find((p) => p.name === projectName);
|
|
71
|
+
resolvedArgs = match ? [match.path] : [];
|
|
72
|
+
} catch {
|
|
73
|
+
resolvedArgs = [];
|
|
74
|
+
}
|
|
75
|
+
return resolvedArgs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function attempt() {
|
|
79
|
+
const args = await resolveArgs();
|
|
80
|
+
if (cancelled) return;
|
|
81
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
82
|
+
detail: { command, args },
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// First attempt after short delay (let WS connect), then retry every 2s
|
|
87
|
+
const initialTimer = setTimeout(() => {
|
|
88
|
+
if (!cancelled) attempt();
|
|
89
|
+
}, 500);
|
|
90
|
+
const retryTimer = setInterval(() => {
|
|
91
|
+
if (!cancelled) attempt();
|
|
92
|
+
}, 2_000);
|
|
93
|
+
|
|
94
|
+
return () => { cancelled = true; clearTimeout(initialTimer); clearInterval(retryTimer); };
|
|
95
|
+
}, [panel, viewType, projectName]);
|
|
96
|
+
|
|
97
|
+
// When project switches while panel exists, re-dispatch command with new project path
|
|
98
|
+
const prevProjectRef = useRef(currentProject);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!panel || !viewType || !currentProject || currentProject === prevProjectRef.current) {
|
|
101
|
+
prevProjectRef.current = currentProject;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
prevProjectRef.current = currentProject;
|
|
105
|
+
// Resolve new project path and dispatch command
|
|
106
|
+
(async () => {
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch("/api/projects");
|
|
109
|
+
const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };
|
|
110
|
+
const match = json.data?.find((p: { name: string }) => p.name === currentProject);
|
|
111
|
+
if (match) {
|
|
112
|
+
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
113
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
114
|
+
detail: { command, args: [match.path] },
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
} catch {}
|
|
118
|
+
})();
|
|
119
|
+
}, [panel, viewType, currentProject]);
|
|
120
|
+
|
|
121
|
+
// Check activation errors for this extension
|
|
122
|
+
const extensionId = metadata?.extensionId as string | undefined;
|
|
123
|
+
const activationError = useExtensionStore((s) => {
|
|
124
|
+
// Direct match by extensionId (most reliable)
|
|
125
|
+
if (extensionId && s.activationErrors[extensionId]) return s.activationErrors[extensionId];
|
|
126
|
+
// Fallback: check by viewType prefix (e.g. "ext-git-graph" for viewType "git-graph")
|
|
127
|
+
if (!viewType) return undefined;
|
|
128
|
+
for (const [extId, error] of Object.entries(s.activationErrors)) {
|
|
129
|
+
if (extId === `ext-${viewType}`) return error;
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Retry handler — re-dispatches the command
|
|
135
|
+
const handleRetry = useCallback(() => {
|
|
136
|
+
setTimedOut(false);
|
|
137
|
+
if (!viewType) return;
|
|
138
|
+
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
139
|
+
(async () => {
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch("/api/projects");
|
|
142
|
+
const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };
|
|
143
|
+
const match = json.data?.find((p) => p.name === projectName);
|
|
144
|
+
const args = match ? [match.path] : [];
|
|
145
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
146
|
+
detail: { command, args },
|
|
147
|
+
}));
|
|
148
|
+
} catch {}
|
|
149
|
+
})();
|
|
150
|
+
}, [viewType, projectName]);
|
|
151
|
+
|
|
152
|
+
// Timeout: if panel doesn't appear within 10s, show error
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (panel) { setTimedOut(false); return; }
|
|
155
|
+
const timer = setTimeout(() => setTimedOut(true), 10_000);
|
|
156
|
+
return () => clearTimeout(timer);
|
|
157
|
+
}, [panel]);
|
|
158
|
+
|
|
36
159
|
// Listen for postMessage from iframe → forward to extension via WS bridge
|
|
37
160
|
useEffect(() => {
|
|
161
|
+
if (!resolvedPanelId) return;
|
|
38
162
|
const handler = (event: MessageEvent) => {
|
|
39
163
|
if (iframeRef.current && event.source === iframeRef.current.contentWindow) {
|
|
40
|
-
// Forward to server via custom event → picked up by useExtensionWs
|
|
41
164
|
window.dispatchEvent(new CustomEvent("ext:webview:send", {
|
|
42
|
-
detail: { panelId, message: event.data },
|
|
165
|
+
detail: { panelId: resolvedPanelId, message: event.data },
|
|
43
166
|
}));
|
|
44
167
|
}
|
|
45
168
|
};
|
|
46
169
|
window.addEventListener("message", handler);
|
|
47
170
|
return () => window.removeEventListener("message", handler);
|
|
48
|
-
}, [
|
|
171
|
+
}, [resolvedPanelId]);
|
|
49
172
|
|
|
50
173
|
// Listen for server→webview messages (dispatched by useExtensionWs)
|
|
51
|
-
// targetOrigin "*" is safe here because sandbox omits allow-same-origin,
|
|
52
|
-
// so iframe origin is opaque "null". MUST restrict if allow-same-origin is ever added.
|
|
53
174
|
useEffect(() => {
|
|
175
|
+
if (!resolvedPanelId) return;
|
|
54
176
|
const handler = (e: Event) => {
|
|
55
177
|
const { panelId: targetId, message } = (e as CustomEvent).detail;
|
|
56
|
-
if (targetId ===
|
|
178
|
+
if (targetId === resolvedPanelId) {
|
|
57
179
|
iframeRef.current?.contentWindow?.postMessage(message, "*");
|
|
58
180
|
}
|
|
59
181
|
};
|
|
60
182
|
window.addEventListener("ext:webview:message", handler);
|
|
61
183
|
return () => window.removeEventListener("ext:webview:message", handler);
|
|
62
|
-
}, [
|
|
184
|
+
}, [resolvedPanelId]);
|
|
63
185
|
|
|
186
|
+
// Loading state — waiting for extension to create the panel
|
|
64
187
|
if (!panel) {
|
|
65
188
|
return (
|
|
66
|
-
<div className="flex items-center justify-center h-full text-sm text-text-subtle">
|
|
67
|
-
|
|
189
|
+
<div className="flex flex-col items-center justify-center h-full gap-3 text-sm text-text-subtle">
|
|
190
|
+
{timedOut ? (
|
|
191
|
+
<>
|
|
192
|
+
<span className="text-destructive font-medium">Extension failed to load</span>
|
|
193
|
+
{activationError && (
|
|
194
|
+
<span className="text-xs text-muted-foreground max-w-md text-center">{activationError}</span>
|
|
195
|
+
)}
|
|
196
|
+
<button
|
|
197
|
+
onClick={handleRetry}
|
|
198
|
+
className="text-xs text-primary hover:underline"
|
|
199
|
+
>
|
|
200
|
+
Retry
|
|
201
|
+
</button>
|
|
202
|
+
</>
|
|
203
|
+
) : (
|
|
204
|
+
<>
|
|
205
|
+
<Loader2 className="size-5 animate-spin" />
|
|
206
|
+
<span>Loading extension...</span>
|
|
207
|
+
</>
|
|
208
|
+
)}
|
|
68
209
|
</div>
|
|
69
210
|
);
|
|
70
211
|
}
|
|
@@ -2,8 +2,9 @@ import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Terminal,
|
|
4
4
|
MessageSquare,
|
|
5
|
-
GitBranch,
|
|
6
5
|
GitCommitHorizontal,
|
|
6
|
+
GitBranch,
|
|
7
|
+
Puzzle,
|
|
7
8
|
Settings,
|
|
8
9
|
Database,
|
|
9
10
|
Search,
|
|
@@ -13,7 +14,8 @@ import {
|
|
|
13
14
|
Loader2,
|
|
14
15
|
Globe,
|
|
15
16
|
Mic,
|
|
16
|
-
|
|
17
|
+
RefreshCw,
|
|
18
|
+
Plus,
|
|
17
19
|
} from "lucide-react";
|
|
18
20
|
import { useTabStore, type TabType } from "@/stores/tab-store";
|
|
19
21
|
import { useProjectStore } from "@/stores/project-store";
|
|
@@ -38,6 +40,19 @@ interface CommandItem {
|
|
|
38
40
|
|
|
39
41
|
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
40
42
|
|
|
43
|
+
/** Map extension icon string names to lucide components */
|
|
44
|
+
const EXT_ICON_MAP: Record<string, React.ElementType> = {
|
|
45
|
+
"git-branch": GitBranch,
|
|
46
|
+
"database": Database,
|
|
47
|
+
"refresh": RefreshCw,
|
|
48
|
+
"plus": Plus,
|
|
49
|
+
"terminal": Terminal,
|
|
50
|
+
"settings": Settings,
|
|
51
|
+
"search": Search,
|
|
52
|
+
"file-code": FileCode,
|
|
53
|
+
"globe": Globe,
|
|
54
|
+
};
|
|
55
|
+
|
|
41
56
|
/** Format a keybinding combo for display (e.g. "Mod+G" → "⌘G" on Mac, "Ctrl+G" on others) */
|
|
42
57
|
function formatShortcut(combo: string): string {
|
|
43
58
|
if (!combo) return "";
|
|
@@ -162,7 +177,6 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
|
|
|
162
177
|
{ id: "chat", label: "New AI Chat", icon: MessageSquare, action: openNewTab("chat", "AI Chat"), keywords: "ai assistant claude", group: "action", shortcut: formatShortcut(getBinding("open-chat")) },
|
|
163
178
|
{ id: "new-file", label: "New File", icon: FilePlus, action: () => { useTabStore.getState().openNewFile(); onClose(); }, keywords: "create untitled blank empty", group: "action", shortcut: formatShortcut(getBinding("new-file")) },
|
|
164
179
|
{ id: "terminal", label: "New Terminal", icon: Terminal, action: openNewTab("terminal", "Terminal"), keywords: "bash shell console", group: "action", shortcut: formatShortcut(getBinding("open-terminal")) },
|
|
165
|
-
{ id: "git-graph", label: "Git Graph", icon: GitBranch, action: openNewTab("git-graph", "Git Graph"), keywords: "branch history log", group: "action", shortcut: formatShortcut(getBinding("open-git-graph")) },
|
|
166
180
|
{ id: "ports", label: "Port Forwarding", icon: Globe, action: openNewTab("ports", "Ports"), keywords: "web preview localhost port forward tunnel url", group: "action" },
|
|
167
181
|
{ id: "postgres", label: "PostgreSQL", icon: Database, action: openNewTab("postgres", "PostgreSQL"), keywords: "database pg sql query", group: "action" },
|
|
168
182
|
{ id: "voice-input", label: "Voice Input", icon: Mic, action: () => { window.dispatchEvent(new CustomEvent("toggle-voice-input")); onClose(); }, keywords: "speech microphone dictate voice", group: "action", shortcut: formatShortcut(getBinding("voice-input")) },
|
|
@@ -180,20 +194,30 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
|
|
|
180
194
|
},
|
|
181
195
|
];
|
|
182
196
|
|
|
183
|
-
// Append extension-contributed commands
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
// Append extension-contributed commands (with keybinding shortcuts, respecting user overrides)
|
|
198
|
+
const extKbs = extContributions?.keybindings ?? [];
|
|
199
|
+
const extCmds: CommandItem[] = (extContributions?.commands ?? []).map((cmd) => {
|
|
200
|
+
const kb = extKbs.find((k) => k.command === cmd.command);
|
|
201
|
+
const overrideCombo = kb ? getBinding(`ext:${kb.command}`) : "";
|
|
202
|
+
const shortcutCombo = overrideCombo || (kb ? ((isMac && kb.mac) ? kb.mac : kb.key) : "");
|
|
203
|
+
return {
|
|
204
|
+
id: `ext:${cmd.command}`,
|
|
205
|
+
label: cmd.title,
|
|
206
|
+
hint: cmd.category,
|
|
207
|
+
icon: (cmd.icon && EXT_ICON_MAP[cmd.icon]) || Puzzle,
|
|
208
|
+
group: "action" as const,
|
|
209
|
+
keywords: `extension ${cmd.command} ${cmd.category ?? ""}`,
|
|
210
|
+
shortcut: shortcutCombo ? formatShortcut(shortcutCombo) : undefined,
|
|
211
|
+
action: () => {
|
|
212
|
+
const args: unknown[] = [];
|
|
213
|
+
if (activeProject?.path) args.push(activeProject.path);
|
|
214
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
215
|
+
detail: { command: cmd.command, args },
|
|
216
|
+
}));
|
|
217
|
+
onClose();
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
});
|
|
197
221
|
|
|
198
222
|
return [...builtIn, ...extCmds];
|
|
199
223
|
}, [activeProject, openTab, onClose, setSidebarActiveTab, sidebarCollapsed, toggleSidebar, getBinding, extContributions]);
|