@hienlh/ppm 0.9.85 → 0.9.86

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 (235) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
  3. package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
  4. package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
  5. package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
  6. package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
  7. package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
  8. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
  9. package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
  10. package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
  11. package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
  12. package/dist/web/assets/channel-wumTB1if.js +1 -0
  13. package/dist/web/assets/chat-tab-BEEd-Km4.js +10 -0
  14. package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
  15. package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
  16. package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
  17. package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
  18. package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
  19. package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
  20. package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
  21. package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
  22. package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
  23. package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
  24. package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
  25. package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
  26. package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
  27. package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
  28. package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
  29. package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
  30. package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
  31. package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
  32. package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
  33. package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
  34. package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
  35. package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
  36. package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
  37. package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
  38. package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
  39. package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
  40. package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
  41. package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
  42. package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
  43. package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
  44. package/dist/web/assets/clone--z5KLAuR.js +1 -0
  45. package/dist/web/assets/code-editor-Ij4p30cr.js +8 -0
  46. package/dist/web/assets/columns-2-IeETSfON.js +1 -0
  47. package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
  48. package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-CwQnOa3E.js} +2 -2
  49. package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
  50. package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
  51. package/dist/web/assets/database-CgTomMxt.js +1 -0
  52. package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-C1UHSgft.js} +2 -2
  53. package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
  54. package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
  55. package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
  56. package/dist/web/assets/diff-viewer-CVx5naBA.js +4 -0
  57. package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
  58. package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
  59. package/dist/web/assets/extension-webview-CHVVpV34.js +3 -0
  60. package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
  61. package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
  62. package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
  63. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
  64. package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
  65. package/dist/web/assets/index-OqgGFmh8.js +26 -0
  66. package/dist/web/assets/index-vA7juDri.css +2 -0
  67. package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
  68. package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
  69. package/dist/web/assets/input-BHj0veau.js +45 -0
  70. package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
  71. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
  72. package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
  73. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
  74. package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
  75. package/dist/web/assets/keybindings-store-BQxgPV5o.js +1 -0
  76. package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
  77. package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
  78. package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-CRy8xw2B.js} +6 -6
  79. package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
  80. package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
  81. package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
  82. package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
  83. package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
  84. package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
  85. package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
  86. package/dist/web/assets/port-forwarding-tab-Biua8ov5.js +1 -0
  87. package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-BcVjCAl4.js} +3 -3
  88. package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
  89. package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
  90. package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
  91. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
  92. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
  93. package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
  94. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
  95. package/dist/web/assets/settings-tab-C9X-N8hE.js +1 -0
  96. package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-BFvRvJn0.js} +1 -1
  97. package/dist/web/assets/sqlite-viewer-CPfvwFl4.js +1 -0
  98. package/dist/web/assets/square-vBdqj0bF.js +1 -0
  99. package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
  100. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
  101. package/dist/web/assets/table-Bi27fEaN.js +1 -0
  102. package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-mWwk_weB.js} +2 -2
  103. package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
  104. package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
  105. package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
  106. package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
  107. package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-CPaeSMAA.js} +1 -1
  108. package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
  109. package/dist/web/assets/x-Dw3TjeY_.js +1 -0
  110. package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
  111. package/dist/web/index.html +18 -22
  112. package/dist/web/sw.js +1 -1
  113. package/docs/codebase-summary.md +134 -11
  114. package/docs/extension-development-guide.md +98 -1
  115. package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
  116. package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
  117. package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
  118. package/docs/journals/260414-2001-bundled-extensions.md +219 -0
  119. package/docs/project-changelog.md +63 -22
  120. package/docs/project-roadmap.md +1 -0
  121. package/docs/system-architecture.md +33 -5
  122. package/package.json +1 -1
  123. package/packages/ext-git-graph/package.json +30 -0
  124. package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
  125. package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
  126. package/packages/ext-git-graph/src/extension.ts +800 -0
  127. package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
  128. package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
  129. package/packages/ext-git-graph/src/types.ts +181 -0
  130. package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
  131. package/packages/ext-git-graph/src/webview-html.ts +2199 -0
  132. package/packages/vscode-compat/src/index.ts +4 -0
  133. package/packages/vscode-compat/src/process.ts +25 -0
  134. package/packages/vscode-compat/src/window.ts +10 -0
  135. package/src/cli/commands/ext-cmd.ts +3 -1
  136. package/src/server/ws/extensions.ts +6 -2
  137. package/src/services/contribution-registry.ts +14 -1
  138. package/src/services/extension-host-worker.ts +7 -3
  139. package/src/services/extension-manifest.ts +18 -1
  140. package/src/services/extension-rpc-handlers.ts +68 -2
  141. package/src/services/extension.service.ts +46 -6
  142. package/src/types/extension-messages.ts +2 -0
  143. package/src/types/extension.ts +8 -0
  144. package/src/web/components/editor/code-editor.tsx +16 -4
  145. package/src/web/components/extensions/extension-webview.tsx +111 -12
  146. package/src/web/components/layout/command-palette.tsx +41 -17
  147. package/src/web/components/layout/editor-panel.tsx +15 -4
  148. package/src/web/components/layout/mobile-nav.tsx +5 -5
  149. package/src/web/components/layout/tab-bar.tsx +2 -3
  150. package/src/web/components/layout/tab-content.tsx +12 -5
  151. package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
  152. package/src/web/hooks/use-extension-ws.ts +22 -4
  153. package/src/web/hooks/use-global-keybindings.ts +24 -2
  154. package/src/web/hooks/use-url-sync.ts +8 -3
  155. package/src/web/stores/keybindings-store.ts +2 -3
  156. package/src/web/stores/panel-store.ts +2 -2
  157. package/src/web/stores/panel-utils.ts +4 -2
  158. package/src/web/stores/tab-store.ts +1 -1
  159. package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
  160. package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
  161. package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
  162. package/dist/web/assets/channel-Cgy1thYT.js +0 -1
  163. package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
  164. package/dist/web/assets/check-ePA3ZvK4.js +0 -1
  165. package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
  166. package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
  167. package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
  168. package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
  169. package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
  170. package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
  171. package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
  172. package/dist/web/assets/clone--C7Tby8z.js +0 -1
  173. package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
  174. package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
  175. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
  176. package/dist/web/assets/database-D1ToEV9d.js +0 -1
  177. package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
  178. package/dist/web/assets/dist-KUoHa6tg.js +0 -1
  179. package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
  180. package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
  181. package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
  182. package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
  183. package/dist/web/assets/index-CkaCzNgO.css +0 -2
  184. package/dist/web/assets/index-Ic5uTu20.js +0 -26
  185. package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
  186. package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
  187. package/dist/web/assets/input-CcbTF6ih.js +0 -45
  188. package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
  189. package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
  190. package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
  191. package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
  192. package/dist/web/assets/plus-Iso5r9vD.js +0 -1
  193. package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
  194. package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
  195. package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
  196. package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
  197. package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
  198. package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
  199. package/dist/web/assets/square-pfn_LYYy.js +0 -1
  200. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
  201. package/dist/web/assets/table-CHv2x_qg.js +0 -1
  202. package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
  203. package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
  204. package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
  205. package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
  206. package/dist/web/assets/x-BXecj-16.js +0 -1
  207. package/src/web/components/git/git-graph-branch-label.tsx +0 -124
  208. package/src/web/components/git/git-graph-constants.ts +0 -185
  209. package/src/web/components/git/git-graph-detail.tsx +0 -107
  210. package/src/web/components/git/git-graph-dialog.tsx +0 -72
  211. package/src/web/components/git/git-graph-row.tsx +0 -167
  212. package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
  213. package/src/web/components/git/git-graph-svg.tsx +0 -54
  214. package/src/web/components/git/git-graph-toolbar.tsx +0 -195
  215. package/src/web/components/git/git-graph.tsx +0 -193
  216. package/src/web/components/git/use-column-resize.ts +0 -33
  217. package/src/web/components/git/use-git-graph.ts +0 -201
  218. /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
  219. /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
  220. /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
  221. /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
  222. /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
  223. /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
  224. /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
  225. /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
  226. /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
  227. /package/dist/web/assets/{lib-CfWBrYll.js → lib-CeBVkQ-7.js} +0 -0
  228. /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
  229. /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
  230. /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
  231. /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
  232. /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
  233. /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
  234. /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
  235. /package/dist/web/assets/{utils-CSCvNZxE.js → utils-DX8jb5qv.js} +0 -0
@@ -1,5 +1,7 @@
1
- import { useRef, useEffect, useCallback } from "react";
1
+ import { useRef, useEffect, useState } 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,146 @@ 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
+ // Timeout: if panel doesn't appear within 10s, show error
122
+ useEffect(() => {
123
+ if (panel) { setTimedOut(false); return; }
124
+ const timer = setTimeout(() => setTimedOut(true), 10_000);
125
+ return () => clearTimeout(timer);
126
+ }, [panel]);
127
+
36
128
  // Listen for postMessage from iframe → forward to extension via WS bridge
37
129
  useEffect(() => {
130
+ if (!resolvedPanelId) return;
38
131
  const handler = (event: MessageEvent) => {
39
132
  if (iframeRef.current && event.source === iframeRef.current.contentWindow) {
40
- // Forward to server via custom event → picked up by useExtensionWs
41
133
  window.dispatchEvent(new CustomEvent("ext:webview:send", {
42
- detail: { panelId, message: event.data },
134
+ detail: { panelId: resolvedPanelId, message: event.data },
43
135
  }));
44
136
  }
45
137
  };
46
138
  window.addEventListener("message", handler);
47
139
  return () => window.removeEventListener("message", handler);
48
- }, [panelId]);
140
+ }, [resolvedPanelId]);
49
141
 
50
142
  // 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
143
  useEffect(() => {
144
+ if (!resolvedPanelId) return;
54
145
  const handler = (e: Event) => {
55
146
  const { panelId: targetId, message } = (e as CustomEvent).detail;
56
- if (targetId === panelId) {
147
+ if (targetId === resolvedPanelId) {
57
148
  iframeRef.current?.contentWindow?.postMessage(message, "*");
58
149
  }
59
150
  };
60
151
  window.addEventListener("ext:webview:message", handler);
61
152
  return () => window.removeEventListener("ext:webview:message", handler);
62
- }, [panelId]);
153
+ }, [resolvedPanelId]);
63
154
 
155
+ // Loading state — waiting for extension to create the panel
64
156
  if (!panel) {
65
157
  return (
66
- <div className="flex items-center justify-center h-full text-sm text-text-subtle">
67
- Webview panel not found
158
+ <div className="flex flex-col items-center justify-center h-full gap-2 text-sm text-text-subtle">
159
+ {timedOut ? (
160
+ <span>Extension failed to load webview panel</span>
161
+ ) : (
162
+ <>
163
+ <Loader2 className="size-5 animate-spin" />
164
+ <span>Loading extension...</span>
165
+ </>
166
+ )}
68
167
  </div>
69
168
  );
70
169
  }
@@ -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]);
@@ -1,8 +1,8 @@
1
1
  import { Suspense, lazy, useEffect, useState, useCallback } from "react";
2
- import { ChevronDown, ChevronUp, Loader2, Terminal, MessageSquare, GitBranch, Pin, PinOff } from "lucide-react";
2
+ import { ChevronDown, ChevronUp, Loader2, Terminal, MessageSquare, FilePlus, Pin, PinOff } from "lucide-react";
3
3
  import { usePanelStore } from "@/stores/panel-store";
4
4
  import { useProjectStore } from "@/stores/project-store";
5
- import type { TabType } from "@/stores/tab-store";
5
+ import { useTabStore, type TabType } from "@/stores/tab-store";
6
6
  import { api, projectUrl } from "@/lib/api-client";
7
7
  import type { SessionInfo } from "../../../types/chat";
8
8
  import { TabBar } from "./tab-bar";
@@ -12,7 +12,7 @@ import { cn } from "@/lib/utils";
12
12
  const QUICK_OPEN_TABS: { type: TabType; label: string; icon: React.ElementType }[] = [
13
13
  { type: "terminal", label: "Terminal", icon: Terminal },
14
14
  { type: "chat", label: "AI Chat", icon: MessageSquare },
15
- { type: "git-graph", label: "Git Graph", icon: GitBranch },
15
+ { type: "editor", label: "New File", icon: FilePlus },
16
16
  ];
17
17
 
18
18
  const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentType<{ metadata?: Record<string, unknown>; tabId?: string }>>> = {
@@ -22,10 +22,10 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
22
22
  database: lazy(() => import("@/components/database/database-viewer").then((m) => ({ default: m.DatabaseViewer }))),
23
23
  sqlite: lazy(() => import("@/components/sqlite/sqlite-viewer").then((m) => ({ default: m.SqliteViewer }))),
24
24
  postgres: lazy(() => import("@/components/postgres/postgres-viewer").then((m) => ({ default: m.PostgresViewer }))),
25
- "git-graph": lazy(() => import("@/components/git/git-graph").then((m) => ({ default: m.GitGraph }))),
26
25
  "git-diff": lazy(() => import("@/components/editor/diff-viewer").then((m) => ({ default: m.DiffViewer }))),
27
26
  settings: lazy(() => import("@/components/settings/settings-tab").then((m) => ({ default: m.SettingsTab }))),
28
27
  ports: lazy(() => import("@/components/ports/port-forwarding-tab").then((m) => ({ default: m.PortForwardingTab }))),
28
+ extension: lazy(() => import("@/components/extensions/extension-webview").then((m) => ({ default: m.ExtensionWebview }))),
29
29
  "extension-webview": lazy(() => import("@/components/extensions/extension-webview").then((m) => ({ default: m.ExtensionWebview }))),
30
30
  };
31
31
 
@@ -62,6 +62,13 @@ export function EditorPanel({ panelId, projectName }: EditorPanelProps) {
62
62
  panel.tabs.map((tab) => {
63
63
  const Component = TAB_COMPONENTS[tab.type];
64
64
  const isActive = tab.id === panel.activeTabId;
65
+ if (!Component) {
66
+ return (
67
+ <div key={tab.id} className={isActive ? "h-full w-full flex items-center justify-center text-muted-foreground" : "hidden"}>
68
+ Unknown tab type: {tab.type}
69
+ </div>
70
+ );
71
+ }
65
72
  return (
66
73
  <div key={tab.id} className={isActive ? "h-full w-full" : "hidden"}>
67
74
  <Suspense fallback={<div className="flex items-center justify-center h-full"><Loader2 className="size-6 animate-spin text-primary" /></div>}>
@@ -143,6 +150,10 @@ function EmptyPanel({ panelId }: { panelId: string }) {
143
150
  }, [activeProject?.name]);
144
151
 
145
152
  function openTab(type: TabType) {
153
+ if (type === "editor") {
154
+ useTabStore.getState().openNewFile();
155
+ return;
156
+ }
146
157
  const needsProject = type !== "settings";
147
158
  const metadata = needsProject && activeProject ? { projectName: activeProject.name } : undefined;
148
159
  usePanelStore.getState().openTab(
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useRef, useCallback, useMemo } from "react";
2
2
  import {
3
- Terminal, MessageSquare, GitBranch, Database,
3
+ Terminal, MessageSquare, Database,
4
4
  FileDiff, FileCode, Settings, Menu, X, ArrowLeft, ArrowRight, SplitSquareVertical, MoveVertical, Layers, Plus,
5
5
  ChevronRight, Globe, Puzzle, Copy, Download, Pencil, Trash2,
6
6
  } from "lucide-react";
@@ -21,14 +21,14 @@ import { FileActions } from "@/components/explorer/file-actions";
21
21
  const NEW_TAB_OPTIONS: { type: TabType; label: string }[] = [
22
22
  { type: "terminal", label: "Terminal" },
23
23
  { type: "chat", label: "AI Chat" },
24
- { type: "git-graph", label: "Git Graph" },
25
24
  { type: "settings", label: "Settings" },
26
25
  ];
27
26
  const NEW_TAB_LABELS: Partial<Record<TabType, string>> = Object.fromEntries(NEW_TAB_OPTIONS.map((o) => [o.type, o.label]));
28
27
 
29
28
  const TAB_ICONS: Record<TabType, React.ElementType> = {
30
29
  terminal: Terminal, chat: MessageSquare, editor: FileCode, database: Database, sqlite: Database, postgres: Database,
31
- "git-graph": GitBranch, "git-diff": FileDiff, settings: Settings, ports: Globe,
30
+ "git-diff": FileDiff, settings: Settings, ports: Globe,
31
+ extension: Puzzle,
32
32
  "extension-webview": Puzzle,
33
33
  };
34
34
 
@@ -141,7 +141,7 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
141
141
  function handleNewTab(type: TabType) {
142
142
  const state = usePanelStore.getState();
143
143
  const firstPanelId = state.grid[0]?.[0] ?? state.focusedPanelId;
144
- const needsProject = type === "git-graph" || type === "git-diff" || type === "terminal" || type === "chat";
144
+ const needsProject = type === "git-diff" || type === "terminal" || type === "chat";
145
145
  const metadata = needsProject ? { projectName: activeProjectForTab?.name } : undefined;
146
146
  state.openTab(
147
147
  { type, title: NEW_TAB_LABELS[type] ?? type, metadata, projectId: activeProjectForTab?.name ?? null, closable: true },
@@ -211,7 +211,7 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
211
211
  <div className="flex-1 min-w-0 relative flex items-center h-12 -ml-4">
212
212
  <div ref={mobileScrollRef} className="flex-1 min-w-0 flex items-center h-12 overflow-x-auto scrollbar-none pl-4">
213
213
  {tabs.map((tab) => {
214
- const Icon = TAB_ICONS[tab.type];
214
+ const Icon = TAB_ICONS[tab.type] || Puzzle;
215
215
  const isActive = tab.id === activeTabId;
216
216
  const sessionId = tab.type === "chat" ? (tab.metadata?.sessionId as string) : undefined;
217
217
  const entry = sessionId ? notifications.get(sessionId) : undefined;
@@ -3,7 +3,6 @@ import {
3
3
  Plus,
4
4
  Terminal,
5
5
  MessageSquare,
6
- GitBranch,
7
6
  FileDiff,
8
7
  Settings,
9
8
  FileCode,
@@ -36,10 +35,10 @@ const TAB_ICONS: Record<TabType, React.ElementType> = {
36
35
  database: Database,
37
36
  sqlite: Database,
38
37
  postgres: Database,
39
- "git-graph": GitBranch,
40
38
  "git-diff": FileDiff,
41
39
  settings: Settings,
42
40
  ports: Globe,
41
+ extension: Puzzle,
43
42
  "extension-webview": Puzzle,
44
43
  };
45
44
 
@@ -194,7 +193,7 @@ export function TabBar({ panelId }: TabBarProps) {
194
193
  key={tab.id}
195
194
  tab={tab}
196
195
  isActive={tab.id === activeTabId}
197
- icon={TAB_ICONS[tab.type]}
196
+ icon={TAB_ICONS[tab.type] || Puzzle}
198
197
  showDropBefore={dropIndex === i}
199
198
  notificationType={notiType}
200
199
  onSelect={() => {
@@ -33,11 +33,6 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
33
33
  default: m.PostgresViewer,
34
34
  })),
35
35
  ),
36
- "git-graph": lazy(() =>
37
- import("@/components/git/git-graph").then((m) => ({
38
- default: m.GitGraph,
39
- })),
40
- ),
41
36
  "git-diff": lazy(() =>
42
37
  import("@/components/editor/diff-viewer").then((m) => ({
43
38
  default: m.DiffViewer,
@@ -53,6 +48,11 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
53
48
  default: m.PortForwardingTab,
54
49
  })),
55
50
  ),
51
+ extension: lazy(() =>
52
+ import("@/components/extensions/extension-webview").then((m) => ({
53
+ default: m.ExtensionWebview,
54
+ })),
55
+ ),
56
56
  "extension-webview": lazy(() =>
57
57
  import("@/components/extensions/extension-webview").then((m) => ({
58
58
  default: m.ExtensionWebview,
@@ -84,6 +84,13 @@ export function TabContent() {
84
84
  {tabs.map((tab) => {
85
85
  const Component = TAB_COMPONENTS[tab.type];
86
86
  const isActive = tab.id === activeTabId;
87
+ if (!Component) {
88
+ return (
89
+ <div key={tab.id} className={isActive ? "h-full w-full flex items-center justify-center text-muted-foreground" : "hidden"}>
90
+ Unknown tab type: {tab.type}
91
+ </div>
92
+ );
93
+ }
87
94
  return (
88
95
  <div
89
96
  key={tab.id}
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useCallback, useRef } from "react";
2
- import { RotateCcw, AlertTriangle, Lock } from "lucide-react";
2
+ import { RotateCcw, AlertTriangle, Lock, Puzzle } from "lucide-react";
3
3
  import { Button } from "@/components/ui/button";
4
4
  import {
5
5
  KEY_ACTIONS,
@@ -8,6 +8,7 @@ import {
8
8
  comboFromEvent,
9
9
  type KeyCategory,
10
10
  } from "@/stores/keybindings-store";
11
+ import { useExtensionStore } from "@/stores/extension-store";
11
12
 
12
13
  const CATEGORIES: { key: KeyCategory; label: string }[] = [
13
14
  { key: "general", label: "General" },
@@ -104,6 +105,9 @@ function ShortcutBadge({
104
105
 
105
106
  export function KeyboardShortcutsSection() {
106
107
  const { getBinding, resetBinding, resetAll, overrides } = useKeybindingsStore();
108
+ const extContributions = useExtensionStore((s) => s.contributions);
109
+ const extKeybindings = extContributions?.keybindings ?? [];
110
+ const extCommands = extContributions?.commands ?? [];
107
111
 
108
112
  return (
109
113
  <div className="space-y-3">
@@ -177,6 +181,47 @@ export function KeyboardShortcutsSection() {
177
181
  </div>
178
182
  );
179
183
  })}
184
+
185
+ {/* Extension-contributed keybindings */}
186
+ {extKeybindings.length > 0 && (
187
+ <div className="space-y-1">
188
+ <span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wide">
189
+ Extensions
190
+ </span>
191
+ <div className="space-y-0.5">
192
+ {extKeybindings.map((kb) => {
193
+ const cmd = extCommands.find((c) => c.command === kb.command);
194
+ const label = cmd?.title ?? kb.command;
195
+ const actionId = `ext:${kb.command}`;
196
+ const currentCombo = getBinding(actionId) || kb.key;
197
+ const isOverridden = actionId in overrides;
198
+ return (
199
+ <div
200
+ key={actionId}
201
+ className="flex items-center justify-between py-1 px-1 rounded hover:bg-surface-elevated/50 transition-colors"
202
+ >
203
+ <div className="flex items-center gap-1.5 min-w-0">
204
+ <Puzzle className="size-3 text-muted-foreground shrink-0" />
205
+ <span className="text-xs text-foreground">{label}</span>
206
+ </div>
207
+ <div className="flex items-center gap-1 shrink-0 ml-2">
208
+ <ShortcutBadge actionId={actionId} combo={currentCombo} />
209
+ {isOverridden && (
210
+ <button
211
+ onClick={() => resetBinding(actionId)}
212
+ className="flex items-center justify-center size-5 rounded text-muted-foreground hover:text-foreground hover:bg-surface-elevated transition-colors"
213
+ title="Reset to default"
214
+ >
215
+ <RotateCcw className="size-3" />
216
+ </button>
217
+ )}
218
+ </div>
219
+ </div>
220
+ );
221
+ })}
222
+ </div>
223
+ </div>
224
+ )}
180
225
  </div>
181
226
  );
182
227
  }
@@ -111,7 +111,8 @@ export function useExtensionWs(enabled = true) {
111
111
  });
112
112
  break;
113
113
 
114
- case "webview:create":
114
+ case "webview:create": {
115
+ const viewTypeSlug = msg.viewType.replace(/\.view$/, "");
115
116
  store.addWebviewPanel({
116
117
  id: msg.panelId,
117
118
  extensionId: msg.extensionId,
@@ -119,15 +120,18 @@ export function useExtensionWs(enabled = true) {
119
120
  title: msg.title,
120
121
  html: "",
121
122
  });
122
- // Open a tab to display the webview panel
123
+ // Open a tab use stable viewType slug as identifier (survives reload)
124
+ // Include projectName so reload can resolve project path for re-trigger
125
+ const currentProject = useTabStore.getState().currentProject;
123
126
  useTabStore.getState().openTab({
124
- type: "extension-webview",
127
+ type: "extension",
125
128
  title: msg.title,
126
129
  projectId: null,
127
130
  closable: true,
128
- metadata: { panelId: msg.panelId, extensionId: msg.extensionId },
131
+ metadata: { viewType: viewTypeSlug, panelId: msg.panelId, extensionId: msg.extensionId, ...(currentProject && { projectName: currentProject }) },
129
132
  });
130
133
  break;
134
+ }
131
135
 
132
136
  case "webview:html":
133
137
  store.updateWebviewPanel(msg.panelId, { html: msg.html });
@@ -142,6 +146,20 @@ export function useExtensionWs(enabled = true) {
142
146
  detail: { panelId: msg.panelId, message: msg.message },
143
147
  }));
144
148
  break;
149
+
150
+ case "tab:open":
151
+ useTabStore.getState().openTab({
152
+ type: (msg as any).tabType,
153
+ title: (msg as any).title,
154
+ projectId: (msg as any).projectId ?? null,
155
+ closable: (msg as any).closable ?? true,
156
+ metadata: (msg as any).metadata,
157
+ });
158
+ break;
159
+
160
+ case "project:switch":
161
+ useTabStore.getState().switchProject((msg as any).projectName);
162
+ break;
145
163
  }
146
164
  });
147
165
 
@@ -2,7 +2,8 @@ import { useEffect, useState, useCallback } from "react";
2
2
  import { useTabStore } from "@/stores/tab-store";
3
3
  import { useSettingsStore } from "@/stores/settings-store";
4
4
  import { useProjectStore } from "@/stores/project-store";
5
- import { useKeybindingsStore } from "@/stores/keybindings-store";
5
+ import { useKeybindingsStore, parseCombo, eventMatchesCombo } from "@/stores/keybindings-store";
6
+ import { useExtensionStore } from "@/stores/extension-store";
6
7
 
7
8
  /** Dispatch this event to open the command palette from anywhere, optionally with initial query */
8
9
  export function openCommandPalette(initialQuery?: string) {
@@ -112,7 +113,6 @@ export function useGlobalKeybindings() {
112
113
  const tabShortcuts: { action: string; type: string; title: string }[] = [
113
114
  { action: "open-chat", type: "chat", title: "AI Chat" },
114
115
  { action: "open-terminal", type: "terminal", title: "Terminal" },
115
- { action: "open-git-graph", type: "git-graph", title: "Git Graph" },
116
116
  ];
117
117
  for (const s of tabShortcuts) {
118
118
  if (match(e, s.action)) {
@@ -176,6 +176,28 @@ export function useGlobalKeybindings() {
176
176
  return;
177
177
  }
178
178
  }
179
+
180
+ // Extension-contributed keybindings (with user override support)
181
+ const extKbs = useExtensionStore.getState().contributions?.keybindings;
182
+ if (extKbs) {
183
+ const mac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
184
+ const { getBinding: getBind } = useKeybindingsStore.getState();
185
+ for (const kb of extKbs) {
186
+ // User override via "ext:<command>" key, fallback to extension default
187
+ const overrideCombo = getBind(`ext:${kb.command}`);
188
+ const combo = overrideCombo || ((mac && kb.mac) ? kb.mac : kb.key);
189
+ if (combo && eventMatchesCombo(e, parseCombo(combo))) {
190
+ e.preventDefault();
191
+ const project = useProjectStore.getState().activeProject;
192
+ const args: unknown[] = [];
193
+ if (project?.path) args.push(project.path);
194
+ window.dispatchEvent(new CustomEvent("ext:command:execute", {
195
+ detail: { command: kb.command, args },
196
+ }));
197
+ return;
198
+ }
199
+ }
200
+ }
179
201
  }
180
202
 
181
203
  // Custom event listener for programmatic opening
@@ -14,7 +14,8 @@ export interface UrlState {
14
14
 
15
15
  const VALID_TAB_TYPES: TabType[] = [
16
16
  "terminal", "chat", "editor", "database", "sqlite",
17
- "postgres", "git-graph", "git-diff", "settings", "ports",
17
+ "postgres", "git-diff", "settings", "ports",
18
+ "extension",
18
19
  ];
19
20
 
20
21
  // ---------------------------------------------------------------------------
@@ -108,7 +109,6 @@ function buildMetadataFromUrl(
108
109
  return { ...(sessionId && { sessionId }), ...(providerId && { providerId }), projectName };
109
110
  }
110
111
  case "terminal": return { terminalIndex: parseInt(identifier ?? "1", 10), projectName };
111
- case "git-graph": return { projectName };
112
112
  case "git-diff": return identifier ? { filePath: identifier, projectName } : null;
113
113
  case "settings": return {};
114
114
  case "database": {
@@ -121,6 +121,7 @@ function buildMetadataFromUrl(
121
121
  return connId ? { connectionId: connId, tableName: tableName ?? "" } : null;
122
122
  }
123
123
  case "ports": return null;
124
+ case "extension": return identifier ? { viewType: identifier, projectName } : null;
124
125
  default: return null;
125
126
  }
126
127
  }
@@ -130,13 +131,17 @@ function buildTitleFromUrl(type: TabType, identifier: string | null): string {
130
131
  case "editor": return identifier?.split("/").pop() ?? "File";
131
132
  case "chat": return "Chat";
132
133
  case "terminal": return `Terminal ${identifier ?? "1"}`;
133
- case "git-graph": return "Git Graph";
134
134
  case "git-diff": return identifier?.split("/").pop() ?? "Diff";
135
135
  case "settings": return "Settings";
136
136
  case "database": return identifier ?? "Database";
137
137
  case "sqlite": return identifier?.split("/").pop() ?? "SQLite";
138
138
  case "postgres": return identifier ?? "PostgreSQL";
139
139
  case "ports": return "Ports";
140
+ case "extension": {
141
+ if (!identifier) return "Extension";
142
+ // "git-graph" → "Git Graph"
143
+ return identifier.split("-").map(w => (w[0]?.toUpperCase() ?? "") + w.slice(1)).join(" ");
144
+ }
140
145
  default: return type;
141
146
  }
142
147
  }