@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.
Files changed (243) hide show
  1. package/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
  2. package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
  3. package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
  4. package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
  5. package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
  6. package/CHANGELOG.md +24 -0
  7. package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
  8. package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
  9. package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
  10. package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
  11. package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
  12. package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
  13. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
  14. package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
  15. package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
  16. package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
  17. package/dist/web/assets/channel-wumTB1if.js +1 -0
  18. package/dist/web/assets/chat-tab-R4gKsnxD.js +10 -0
  19. package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
  20. package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
  21. package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
  22. package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
  23. package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
  24. package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
  25. package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
  26. package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
  27. package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
  28. package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
  29. package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
  30. package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
  31. package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
  32. package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
  33. package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
  34. package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
  35. package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
  36. package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
  37. package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
  38. package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
  39. package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
  40. package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
  41. package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
  42. package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
  43. package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
  44. package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
  45. package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
  46. package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
  47. package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
  48. package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
  49. package/dist/web/assets/clone--z5KLAuR.js +1 -0
  50. package/dist/web/assets/code-editor-Br0vzTOy.js +8 -0
  51. package/dist/web/assets/columns-2-IeETSfON.js +1 -0
  52. package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
  53. package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
  54. package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-BZRICDP0.js} +2 -2
  55. package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
  56. package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
  57. package/dist/web/assets/database-CgTomMxt.js +1 -0
  58. package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-DaUoQ-oR.js} +2 -2
  59. package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
  60. package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
  61. package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
  62. package/dist/web/assets/diff-viewer-BzvK3gAE.js +4 -0
  63. package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
  64. package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
  65. package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
  66. package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
  67. package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
  68. package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
  69. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
  70. package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
  71. package/dist/web/assets/index-CKsEzQ4f.js +26 -0
  72. package/dist/web/assets/index-Chf0otez.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
  74. package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
  75. package/dist/web/assets/input-BHj0veau.js +45 -0
  76. package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
  77. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
  78. package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
  79. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
  80. package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
  81. package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
  82. package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
  83. package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
  84. package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-DSYnGywb.js} +6 -6
  85. package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
  86. package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
  87. package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
  88. package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
  89. package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
  90. package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
  91. package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
  92. package/dist/web/assets/port-forwarding-tab-vmqDKmk2.js +1 -0
  93. package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-0lIAosrr.js} +3 -3
  94. package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
  95. package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
  96. package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
  97. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
  98. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
  99. package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
  100. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
  101. package/dist/web/assets/settings-tab-CMnv1fce.js +1 -0
  102. package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-Bc2hAwqT.js} +1 -1
  103. package/dist/web/assets/sqlite-viewer-B60MS2Dy.js +1 -0
  104. package/dist/web/assets/square-vBdqj0bF.js +1 -0
  105. package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
  106. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
  107. package/dist/web/assets/table-Bi27fEaN.js +1 -0
  108. package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-CCJoLstH.js} +2 -2
  109. package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
  110. package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
  111. package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
  112. package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
  113. package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-BJK48EmK.js} +1 -1
  114. package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
  115. package/dist/web/assets/x-Dw3TjeY_.js +1 -0
  116. package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
  117. package/dist/web/index.html +18 -22
  118. package/dist/web/sw.js +1 -1
  119. package/docs/codebase-summary.md +169 -13
  120. package/docs/extension-development-guide.md +98 -1
  121. package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
  122. package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
  123. package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
  124. package/docs/journals/260414-2001-bundled-extensions.md +219 -0
  125. package/docs/project-changelog.md +123 -21
  126. package/docs/project-roadmap.md +4 -2
  127. package/docs/system-architecture.md +77 -6
  128. package/package.json +1 -1
  129. package/packages/ext-git-graph/package.json +30 -0
  130. package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
  131. package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
  132. package/packages/ext-git-graph/src/extension.ts +921 -0
  133. package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
  134. package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
  135. package/packages/ext-git-graph/src/types.ts +192 -0
  136. package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
  137. package/packages/ext-git-graph/src/webview-html.ts +2417 -0
  138. package/packages/vscode-compat/src/index.ts +4 -0
  139. package/packages/vscode-compat/src/process.ts +25 -0
  140. package/packages/vscode-compat/src/window.ts +10 -0
  141. package/src/cli/commands/ext-cmd.ts +3 -1
  142. package/src/server/ws/extensions.ts +34 -4
  143. package/src/services/contribution-registry.ts +14 -1
  144. package/src/services/extension-host-worker.ts +12 -3
  145. package/src/services/extension-manifest.ts +18 -1
  146. package/src/services/extension-rpc-handlers.ts +68 -2
  147. package/src/services/extension.service.ts +63 -9
  148. package/src/types/extension-messages.ts +3 -1
  149. package/src/types/extension.ts +8 -0
  150. package/src/web/components/editor/code-editor.tsx +16 -4
  151. package/src/web/components/editor/conflict-editor.tsx +368 -0
  152. package/src/web/components/extensions/extension-webview.tsx +153 -12
  153. package/src/web/components/layout/command-palette.tsx +41 -17
  154. package/src/web/components/layout/editor-panel.tsx +16 -4
  155. package/src/web/components/layout/mobile-nav.tsx +6 -5
  156. package/src/web/components/layout/tab-bar.tsx +3 -3
  157. package/src/web/components/layout/tab-content.tsx +17 -5
  158. package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
  159. package/src/web/hooks/use-extension-ws.ts +30 -4
  160. package/src/web/hooks/use-global-keybindings.ts +24 -2
  161. package/src/web/hooks/use-url-sync.ts +8 -3
  162. package/src/web/stores/extension-store.ts +8 -0
  163. package/src/web/stores/keybindings-store.ts +2 -3
  164. package/src/web/stores/panel-store.ts +2 -2
  165. package/src/web/stores/panel-utils.ts +6 -2
  166. package/src/web/stores/tab-store.ts +3 -2
  167. package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
  168. package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
  169. package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
  170. package/dist/web/assets/channel-Cgy1thYT.js +0 -1
  171. package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
  172. package/dist/web/assets/check-ePA3ZvK4.js +0 -1
  173. package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
  174. package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
  175. package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
  176. package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
  177. package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
  178. package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
  179. package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
  180. package/dist/web/assets/clone--C7Tby8z.js +0 -1
  181. package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
  182. package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
  183. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
  184. package/dist/web/assets/database-D1ToEV9d.js +0 -1
  185. package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
  186. package/dist/web/assets/dist-KUoHa6tg.js +0 -1
  187. package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
  188. package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
  189. package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
  190. package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
  191. package/dist/web/assets/index-CkaCzNgO.css +0 -2
  192. package/dist/web/assets/index-Ic5uTu20.js +0 -26
  193. package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
  194. package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
  195. package/dist/web/assets/input-CcbTF6ih.js +0 -45
  196. package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
  197. package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
  198. package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
  199. package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
  200. package/dist/web/assets/plus-Iso5r9vD.js +0 -1
  201. package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
  202. package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
  203. package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
  204. package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
  205. package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
  206. package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
  207. package/dist/web/assets/square-pfn_LYYy.js +0 -1
  208. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
  209. package/dist/web/assets/table-CHv2x_qg.js +0 -1
  210. package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
  211. package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
  212. package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
  213. package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
  214. package/dist/web/assets/x-BXecj-16.js +0 -1
  215. package/src/web/components/git/git-graph-branch-label.tsx +0 -124
  216. package/src/web/components/git/git-graph-constants.ts +0 -185
  217. package/src/web/components/git/git-graph-detail.tsx +0 -107
  218. package/src/web/components/git/git-graph-dialog.tsx +0 -72
  219. package/src/web/components/git/git-graph-row.tsx +0 -167
  220. package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
  221. package/src/web/components/git/git-graph-svg.tsx +0 -54
  222. package/src/web/components/git/git-graph-toolbar.tsx +0 -195
  223. package/src/web/components/git/git-graph.tsx +0 -193
  224. package/src/web/components/git/use-column-resize.ts +0 -33
  225. package/src/web/components/git/use-git-graph.ts +0 -201
  226. /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
  227. /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
  228. /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
  229. /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
  230. /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
  231. /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
  232. /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
  233. /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
  234. /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
  235. /package/dist/web/assets/{lib-CfWBrYll.js → lib-DSLzfeW0.js} +0 -0
  236. /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
  237. /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
  238. /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
  239. /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
  240. /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
  241. /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
  242. /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
  243. /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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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
- * Renders as a tab component in the editor panel system.
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 panel = useExtensionStore((s) => panelId ? s.webviewPanels[panelId] : undefined);
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
- }, [panelId]);
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 === panelId) {
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
- }, [panelId]);
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
- Webview panel not found
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
- Puzzle,
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 extCmds: CommandItem[] = (extContributions?.commands ?? []).map((cmd) => ({
185
- id: `ext:${cmd.command}`,
186
- label: cmd.title,
187
- hint: cmd.category,
188
- icon: Puzzle,
189
- group: "action" as const,
190
- keywords: `extension ${cmd.command} ${cmd.category ?? ""}`,
191
- action: () => {
192
- // Phase 4: execute via WS bridge
193
- console.log("[CmdPalette] ext command:", cmd.command);
194
- onClose();
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]);