@hienlh/ppm 0.9.84 → 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 (252) hide show
  1. package/260413-1354-new-file-editor-tab/reports/code-reviewer-260413-1420-new-file-tab-review.md +210 -0
  2. package/CHANGELOG.md +23 -0
  3. package/bun.lock +259 -9
  4. package/dist/web/assets/{_basePickBy-5PGDJbfF.js → _basePickBy-Bj0dI1ei.js} +1 -1
  5. package/dist/web/assets/{_baseUniq-BT4Ow4Kk.js → _baseUniq-CyzdZeQH.js} +1 -1
  6. package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
  7. package/dist/web/assets/{api-settings-Bn-bIxD1.js → api-settings-CUxg9RE5.js} +1 -1
  8. package/dist/web/assets/{arc-BAOivWpI.js → arc-CxgHJ7Z4.js} +1 -1
  9. package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
  10. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-Z-4eN4za.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
  11. package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
  12. package/dist/web/assets/{blockDiagram-WCTKOSBZ-BCLqzhuZ.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
  13. package/dist/web/assets/{c4Diagram-IC4MRINW-0Vp0Jeas.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
  14. package/dist/web/assets/channel-wumTB1if.js +1 -0
  15. package/dist/web/assets/chat-tab-BEEd-Km4.js +10 -0
  16. package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
  17. package/dist/web/assets/{chunk-4BX2VUAB-D4tOov49.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
  18. package/dist/web/assets/{chunk-55IACEB6-DJ6BynZ4.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
  19. package/dist/web/assets/{chunk-7E7YKBS2-CiyUJxNI.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
  20. package/dist/web/assets/{chunk-7R4GIKGN-Dv-4cAYn.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
  21. package/dist/web/assets/{chunk-C72U2L5F-D21mS_6G.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
  22. package/dist/web/assets/{chunk-EGIJ26TM-DzqmU2Z7.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
  23. package/dist/web/assets/{chunk-FMBD7UC4-DXncblvW.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
  24. package/dist/web/assets/{chunk-GEFDOKGD-D-pKjlVd.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
  25. package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
  26. package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
  27. package/dist/web/assets/{chunk-JSJVCQXG-99JzIdPr.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
  28. package/dist/web/assets/{chunk-KX2RTZJC-CRq1OBZv.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
  29. package/dist/web/assets/{chunk-KYZI473N-Bb0MCaIO.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
  30. package/dist/web/assets/{chunk-L3YUKLVL-C7qGJrfV.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
  31. package/dist/web/assets/{chunk-MX3YWQON-BpS_PtKp.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
  32. package/dist/web/assets/{chunk-NQ4KR5QH-z_blpjxi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
  33. package/dist/web/assets/{chunk-O4XLMI2P-nDhi_cVu.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
  34. package/dist/web/assets/{chunk-OZEHJAEY-BXhYx3nO.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
  35. package/dist/web/assets/{chunk-PQ6SQG4A-TF58UVMU.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
  36. package/dist/web/assets/{chunk-PU5JKC2W-ek7k4QVB.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
  37. package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
  38. package/dist/web/assets/{chunk-R5LLSJPH-CFwSJijQ.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
  39. package/dist/web/assets/{chunk-WL4C6EOR-ByUrSRin.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
  40. package/dist/web/assets/{chunk-XIRO2GV7-Djlmrely.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
  41. package/dist/web/assets/{chunk-XPW4576I-BPQQBakK.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
  42. package/dist/web/assets/{chunk-XZSTWKYB-DxAOx4hG.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
  43. package/dist/web/assets/{chunk-YBOYWFTD-rQG3QH5s.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
  44. package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
  45. package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
  46. package/dist/web/assets/clone--z5KLAuR.js +1 -0
  47. package/dist/web/assets/code-editor-Ij4p30cr.js +8 -0
  48. package/dist/web/assets/columns-2-IeETSfON.js +1 -0
  49. package/dist/web/assets/{cose-bilkent-S5V4N54A-B_AWZsOP.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
  50. package/dist/web/assets/{csv-preview-D2pJJj3K.js → csv-preview-CwQnOa3E.js} +2 -2
  51. package/dist/web/assets/{dagre-DHq9bhnd.js → dagre-CkhlMHnx.js} +1 -1
  52. package/dist/web/assets/{dagre-KLK3FWXG-BdJr7Byp.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
  53. package/dist/web/assets/database-CgTomMxt.js +1 -0
  54. package/dist/web/assets/{database-viewer-Camu01H4.js → database-viewer-C1UHSgft.js} +2 -2
  55. package/dist/web/assets/{diagram-E7M64L7V-_db4pBVA.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
  56. package/dist/web/assets/{diagram-IFDJBPK2-xKoeuiJx.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
  57. package/dist/web/assets/{diagram-P4PSJMXO-C8tjJsev.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
  58. package/dist/web/assets/diff-viewer-CVx5naBA.js +4 -0
  59. package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
  60. package/dist/web/assets/{erDiagram-INFDFZHY-BSh2z9Df.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
  61. package/dist/web/assets/extension-webview-CHVVpV34.js +3 -0
  62. package/dist/web/assets/{flowDiagram-PKNHOUZH-oYaovqyp.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
  63. package/dist/web/assets/{ganttDiagram-A5KZAMGK-DmL26q2P.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
  64. package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
  65. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-CMoukSrY.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
  66. package/dist/web/assets/{graphlib-BcsNnGcW.js → graphlib-BWe1iK_s.js} +1 -1
  67. package/dist/web/assets/index-OqgGFmh8.js +26 -0
  68. package/dist/web/assets/index-vA7juDri.css +2 -0
  69. package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
  70. package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
  71. package/dist/web/assets/input-BHj0veau.js +45 -0
  72. package/dist/web/assets/{isEmpty-bnrF3Qbc.js → isEmpty-BfLnxq-B.js} +1 -1
  73. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D05_LyL7.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
  74. package/dist/web/assets/{journeyDiagram-4ABVD52K-B_L20qMe.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
  75. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
  76. package/dist/web/assets/{kanban-definition-K7BYSVSG-CZ535BbZ.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
  77. package/dist/web/assets/keybindings-store-BQxgPV5o.js +1 -0
  78. package/dist/web/assets/{line-CVvo3dRu.js → line-CSuSrJ9J.js} +1 -1
  79. package/dist/web/assets/{linear-DP4mkX3m.js → linear-DFN_MPsw.js} +1 -1
  80. package/dist/web/assets/markdown-renderer-CRy8xw2B.js +306 -0
  81. package/dist/web/assets/{mermaid-parser.core-C7UwoIh6.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
  82. package/dist/web/assets/{mindmap-definition-YRQLILUH-x0MTutJp.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
  83. package/dist/web/assets/{ordinal-_K3x1fkz.js → ordinal-DpFn432U.js} +1 -1
  84. package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
  85. package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
  86. package/dist/web/assets/{pieDiagram-SKSYHLDU-C1Gjrtzy.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
  87. package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
  88. package/dist/web/assets/port-forwarding-tab-Biua8ov5.js +1 -0
  89. package/dist/web/assets/{postgres-viewer-BQdPMowm.js → postgres-viewer-BcVjCAl4.js} +3 -3
  90. package/dist/web/assets/{quadrantDiagram-337W2JSQ-C8bzJCjQ.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
  91. package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
  92. package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
  93. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-pQyah6WB.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
  94. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-T6RgG-N8.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
  95. package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
  96. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-BQDJ4CVs.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
  97. package/dist/web/assets/settings-tab-C9X-N8hE.js +1 -0
  98. package/dist/web/assets/{sql-query-editor-CY61vWBg.js → sql-query-editor-BFvRvJn0.js} +1 -1
  99. package/dist/web/assets/sqlite-viewer-CPfvwFl4.js +1 -0
  100. package/dist/web/assets/square-vBdqj0bF.js +1 -0
  101. package/dist/web/assets/{stateDiagram-RAJIS63D-66vhiIuk.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
  102. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
  103. package/dist/web/assets/table-Bi27fEaN.js +1 -0
  104. package/dist/web/assets/{terminal-tab-TIJmxHl6.js → terminal-tab-mWwk_weB.js} +2 -2
  105. package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
  106. package/dist/web/assets/{timeline-definition-YZTLITO2-DwZqB3nn.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
  107. package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
  108. package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
  109. package/dist/web/assets/{use-monaco-theme-BHn-LEm7.js → use-monaco-theme-CPaeSMAA.js} +1 -1
  110. package/dist/web/assets/{vennDiagram-LZ73GAT5-s9Z71fz-.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
  111. package/dist/web/assets/x-Dw3TjeY_.js +1 -0
  112. package/dist/web/assets/{xychartDiagram-JWTSCODW-DRa_TH4B.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
  113. package/dist/web/index.html +18 -12
  114. package/dist/web/sw.js +1 -1
  115. package/docs/codebase-summary.md +134 -11
  116. package/docs/extension-development-guide.md +98 -1
  117. package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
  118. package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
  119. package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
  120. package/docs/journals/260414-2001-bundled-extensions.md +219 -0
  121. package/docs/project-changelog.md +63 -22
  122. package/docs/project-roadmap.md +1 -0
  123. package/docs/system-architecture.md +33 -5
  124. package/package.json +9 -3
  125. package/packages/ext-git-graph/package.json +30 -0
  126. package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
  127. package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
  128. package/packages/ext-git-graph/src/extension.ts +800 -0
  129. package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
  130. package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
  131. package/packages/ext-git-graph/src/types.ts +181 -0
  132. package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
  133. package/packages/ext-git-graph/src/webview-html.ts +2199 -0
  134. package/packages/vscode-compat/src/index.ts +4 -0
  135. package/packages/vscode-compat/src/process.ts +25 -0
  136. package/packages/vscode-compat/src/window.ts +10 -0
  137. package/src/cli/commands/ext-cmd.ts +3 -1
  138. package/src/server/index.ts +1 -1
  139. package/src/server/ws/extensions.ts +6 -2
  140. package/src/services/contribution-registry.ts +14 -1
  141. package/src/services/extension-host-worker.ts +7 -3
  142. package/src/services/extension-manifest.ts +18 -1
  143. package/src/services/extension-rpc-handlers.ts +68 -2
  144. package/src/services/extension.service.ts +46 -6
  145. package/src/types/extension-messages.ts +2 -0
  146. package/src/types/extension.ts +8 -0
  147. package/src/web/components/editor/code-editor.tsx +83 -8
  148. package/src/web/components/editor/save-as-dialog.tsx +75 -0
  149. package/src/web/components/extensions/extension-webview.tsx +111 -12
  150. package/src/web/components/layout/command-palette.tsx +43 -17
  151. package/src/web/components/layout/draggable-tab.tsx +120 -67
  152. package/src/web/components/layout/editor-panel.tsx +15 -4
  153. package/src/web/components/layout/mobile-nav.tsx +74 -7
  154. package/src/web/components/layout/tab-bar.tsx +76 -4
  155. package/src/web/components/layout/tab-content.tsx +12 -5
  156. package/src/web/components/layout/upgrade-banner.tsx +3 -0
  157. package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
  158. package/src/web/components/shared/markdown-code-block.tsx +142 -0
  159. package/src/web/components/shared/markdown-context.ts +20 -0
  160. package/src/web/components/shared/markdown-renderer.tsx +113 -288
  161. package/src/web/hooks/use-extension-ws.ts +22 -4
  162. package/src/web/hooks/use-global-keybindings.ts +31 -2
  163. package/src/web/hooks/use-url-sync.ts +8 -3
  164. package/src/web/main.tsx +1 -0
  165. package/src/web/stores/keybindings-store.ts +3 -3
  166. package/src/web/stores/panel-store.ts +2 -2
  167. package/src/web/stores/panel-utils.ts +17 -2
  168. package/src/web/stores/tab-store.ts +17 -1
  169. package/src/web/styles/globals.css +6 -0
  170. package/.opencode/.env.example +0 -98
  171. package/.opencode/skills/ads-management/scripts/.env.example +0 -13
  172. package/.opencode/skills/ai-multimodal/.env.example +0 -230
  173. package/.opencode/skills/cip-design/.env.example +0 -6
  174. package/.opencode/skills/devops/.env.example +0 -76
  175. package/.opencode/skills/docs-seeker/.env.example +0 -15
  176. package/.opencode/skills/elevenlabs/.env.example +0 -3
  177. package/.opencode/skills/marketing-dashboard/.env.example +0 -15
  178. package/.opencode/skills/marketing-dashboard/app/.env.example +0 -2
  179. package/.opencode/skills/marketing-dashboard/server/.env.example +0 -2
  180. package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +0 -70
  181. package/.opencode/skills/mcp-management/scripts/dist/cli.js +0 -160
  182. package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +0 -183
  183. package/.opencode/skills/payment-integration/scripts/.env.example +0 -20
  184. package/.opencode/skills/sequential-thinking/.env.example +0 -8
  185. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +0 -1
  186. package/dist/web/assets/arrow-up-BYhx9ckd.js +0 -1
  187. package/dist/web/assets/channel-By7bn0Yq.js +0 -1
  188. package/dist/web/assets/chat-tab-CT2XUgsc.js +0 -10
  189. package/dist/web/assets/chevron-right-4zq1jPv6.js +0 -1
  190. package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +0 -2
  191. package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +0 -1
  192. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +0 -1
  193. package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +0 -1
  194. package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +0 -1
  195. package/dist/web/assets/clone-LRxlvnMj.js +0 -1
  196. package/dist/web/assets/code-editor-DQiPtcNd.js +0 -8
  197. package/dist/web/assets/columns-2-BoZAN-iw.js +0 -1
  198. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
  199. package/dist/web/assets/diff-viewer-CTwcVIP_.js +0 -4
  200. package/dist/web/assets/dist-DIV6WgAG.js +0 -41
  201. package/dist/web/assets/extension-webview-pU1xJyoc.js +0 -3
  202. package/dist/web/assets/git-graph-BnFbmpom.js +0 -1
  203. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +0 -1
  204. package/dist/web/assets/index-CP9KnaGh.js +0 -30
  205. package/dist/web/assets/index-Cxz7oGXY.css +0 -2
  206. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +0 -1
  207. package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +0 -2
  208. package/dist/web/assets/jsx-runtime-kMwlnEGE.js +0 -1
  209. package/dist/web/assets/keybindings-store-DdhEeehv.js +0 -1
  210. package/dist/web/assets/markdown-renderer-BjYurPV4.js +0 -326
  211. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +0 -1
  212. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +0 -1
  213. package/dist/web/assets/port-forwarding-tab-Bgr8dmsw.js +0 -1
  214. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +0 -1
  215. package/dist/web/assets/settings-tab-BNoboN6E.js +0 -1
  216. package/dist/web/assets/sqlite-viewer-srSbGg1D.js +0 -1
  217. package/dist/web/assets/square-oPKIkJiw.js +0 -1
  218. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +0 -1
  219. package/dist/web/assets/table-DFevCOMd.js +0 -1
  220. package/dist/web/assets/tag-CXMT0QB6.js +0 -1
  221. package/dist/web/assets/text-wrap-BWNOVswA.js +0 -1
  222. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +0 -1
  223. package/dist/web/assets/x-D2_KzIET.js +0 -1
  224. package/src/web/components/git/git-graph-branch-label.tsx +0 -124
  225. package/src/web/components/git/git-graph-constants.ts +0 -185
  226. package/src/web/components/git/git-graph-detail.tsx +0 -107
  227. package/src/web/components/git/git-graph-dialog.tsx +0 -72
  228. package/src/web/components/git/git-graph-row.tsx +0 -167
  229. package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
  230. package/src/web/components/git/git-graph-svg.tsx +0 -54
  231. package/src/web/components/git/git-graph-toolbar.tsx +0 -195
  232. package/src/web/components/git/git-graph.tsx +0 -193
  233. package/src/web/components/git/use-column-resize.ts +0 -33
  234. package/src/web/components/git/use-git-graph.ts +0 -201
  235. /package/dist/web/assets/{api-client-BfBM3I7n.js → api-client-BvxmRZUi.js} +0 -0
  236. /package/dist/web/assets/{array-B9UHiPd-.js → array-BFDiaBgf.js} +0 -0
  237. /package/dist/web/assets/{csv-parser-CNNw2RVA.js → csv-parser-i7fjqP2H.js} +0 -0
  238. /package/dist/web/assets/{cytoscape.esm-BW-DbntU.js → cytoscape.esm-C8i2jUzT.js} +0 -0
  239. /package/dist/web/assets/{defaultLocale-5eAKkKJC.js → defaultLocale-ZeknFqNe.js} +0 -0
  240. /package/dist/web/assets/{dist-CSJdAyA9.js → dist-DZmJeHOA.js} +0 -0
  241. /package/dist/web/assets/{init-DlZdxViB.js → init-0VJVrkRJ.js} +0 -0
  242. /package/dist/web/assets/{isArrayLikeObject-B_v2FtYn.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
  243. /package/dist/web/assets/{katex-Bqvo_ZG0.js → katex-DR0kdMDv.js} +0 -0
  244. /package/dist/web/assets/{lib-DurwGtQO.js → lib-CeBVkQ-7.js} +0 -0
  245. /package/dist/web/assets/{math-069Z4SuC.js → math-CRc16Nj6.js} +0 -0
  246. /package/dist/web/assets/{path-6uRLdFF7.js → path-INs8XTPH.js} +0 -0
  247. /package/dist/web/assets/{preload-helper-Bf_JiD2A.js → preload-helper-mr3rCizq.js} +0 -0
  248. /package/dist/web/assets/{react-SKk5z-bm.js → react-0tkk-ztn.js} +0 -0
  249. /package/dist/web/assets/{rough.esm-JX0wREDd.js → rough.esm-eLccZ4OJ.js} +0 -0
  250. /package/dist/web/assets/{sql-completion-provider-DM9Qov6L.js → sql-completion-provider-B8uUWWej.js} +0 -0
  251. /package/dist/web/assets/{src-BqX54PbV.js → src-CqyWLlNZ.js} +0 -0
  252. /package/dist/web/assets/{utils-BNytJOb1.js → utils-DX8jb5qv.js} +0 -0
@@ -0,0 +1,75 @@
1
+ import { useState, useCallback } from "react";
2
+ import {
3
+ Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
4
+ } from "@/components/ui/dialog";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+ import { FileBrowserPicker } from "@/components/ui/file-browser-picker";
8
+ import { useProjectStore } from "@/stores/project-store";
9
+
10
+ interface SaveAsDialogProps {
11
+ open: boolean;
12
+ defaultName: string;
13
+ content: string;
14
+ onSave: (fullPath: string, content: string) => void;
15
+ onCancel: () => void;
16
+ }
17
+
18
+ export function SaveAsDialog({ open, defaultName, content, onSave, onCancel }: SaveAsDialogProps) {
19
+ const [filename, setFilename] = useState(defaultName);
20
+ const [showPicker, setShowPicker] = useState(false);
21
+ const [error, setError] = useState("");
22
+ const activeProject = useProjectStore((s) => s.activeProject);
23
+
24
+ const validateAndProceed = useCallback(() => {
25
+ const trimmed = filename.trim();
26
+ if (!trimmed) { setError("Filename cannot be empty"); return; }
27
+ if (/[/\\]/.test(trimmed)) { setError("Filename cannot contain / or \\"); return; }
28
+ setError("");
29
+ setShowPicker(true);
30
+ }, [filename]);
31
+
32
+ const handleFolderSelect = useCallback((dirPath: string) => {
33
+ const sep = dirPath.includes("\\") ? "\\" : "/";
34
+ const fullPath = dirPath.endsWith(sep) ? `${dirPath}${filename.trim()}` : `${dirPath}${sep}${filename.trim()}`;
35
+ onSave(fullPath, content);
36
+ }, [filename, content, onSave]);
37
+
38
+ if (showPicker) {
39
+ return (
40
+ <FileBrowserPicker
41
+ open
42
+ mode="folder"
43
+ root={activeProject?.path}
44
+ title={`Save "${filename.trim()}" to...`}
45
+ onSelect={handleFolderSelect}
46
+ onCancel={() => setShowPicker(false)}
47
+ />
48
+ );
49
+ }
50
+
51
+ return (
52
+ <Dialog open={open} onOpenChange={(v) => { if (!v) onCancel(); }}>
53
+ <DialogContent className="sm:max-w-md">
54
+ <DialogHeader>
55
+ <DialogTitle>Save As</DialogTitle>
56
+ </DialogHeader>
57
+ <div className="flex flex-col gap-2 py-2">
58
+ <label className="text-sm text-muted-foreground">Filename</label>
59
+ <Input
60
+ value={filename}
61
+ onChange={(e) => { setFilename(e.target.value); setError(""); }}
62
+ onKeyDown={(e) => { if (e.key === "Enter") validateAndProceed(); }}
63
+ placeholder="e.g. my-file.ts"
64
+ autoFocus
65
+ />
66
+ {error && <p className="text-xs text-destructive">{error}</p>}
67
+ </div>
68
+ <DialogFooter>
69
+ <Button variant="outline" onClick={onCancel}>Cancel</Button>
70
+ <Button onClick={validateAndProceed}>Choose Folder...</Button>
71
+ </DialogFooter>
72
+ </DialogContent>
73
+ </Dialog>
74
+ );
75
+ }
@@ -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,17 +2,20 @@ 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,
10
11
  FileCode,
12
+ FilePlus,
11
13
  FolderOpen,
12
14
  Loader2,
13
15
  Globe,
14
16
  Mic,
15
- Puzzle,
17
+ RefreshCw,
18
+ Plus,
16
19
  } from "lucide-react";
17
20
  import { useTabStore, type TabType } from "@/stores/tab-store";
18
21
  import { useProjectStore } from "@/stores/project-store";
@@ -37,6 +40,19 @@ interface CommandItem {
37
40
 
38
41
  const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
39
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
+
40
56
  /** Format a keybinding combo for display (e.g. "Mod+G" → "⌘G" on Mac, "Ctrl+G" on others) */
41
57
  function formatShortcut(combo: string): string {
42
58
  if (!combo) return "";
@@ -159,8 +175,8 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
159
175
 
160
176
  const builtIn: CommandItem[] = [
161
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")) },
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")) },
162
179
  { id: "terminal", label: "New Terminal", icon: Terminal, action: openNewTab("terminal", "Terminal"), keywords: "bash shell console", group: "action", shortcut: formatShortcut(getBinding("open-terminal")) },
163
- { 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")) },
164
180
  { id: "ports", label: "Port Forwarding", icon: Globe, action: openNewTab("ports", "Ports"), keywords: "web preview localhost port forward tunnel url", group: "action" },
165
181
  { id: "postgres", label: "PostgreSQL", icon: Database, action: openNewTab("postgres", "PostgreSQL"), keywords: "database pg sql query", group: "action" },
166
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")) },
@@ -178,20 +194,30 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
178
194
  },
179
195
  ];
180
196
 
181
- // Append extension-contributed commands
182
- const extCmds: CommandItem[] = (extContributions?.commands ?? []).map((cmd) => ({
183
- id: `ext:${cmd.command}`,
184
- label: cmd.title,
185
- hint: cmd.category,
186
- icon: Puzzle,
187
- group: "action" as const,
188
- keywords: `extension ${cmd.command} ${cmd.category ?? ""}`,
189
- action: () => {
190
- // Phase 4: execute via WS bridge
191
- console.log("[CmdPalette] ext command:", cmd.command);
192
- onClose();
193
- },
194
- }));
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
+ });
195
221
 
196
222
  return [...builtIn, ...extCmds];
197
223
  }, [activeProject, openTab, onClose, setSidebarActiveTab, sidebarCollapsed, toggleSidebar, getBinding, extContributions]);
@@ -1,9 +1,16 @@
1
1
  import { useState, useRef, useEffect } from "react";
2
- import { X } from "lucide-react";
2
+ import { X, Download } from "lucide-react";
3
3
  import type { Tab, TabType } from "@/stores/tab-store";
4
4
  import { cn } from "@/lib/utils";
5
5
  import { isDarkColor } from "@/lib/color-utils";
6
6
  import { notificationColor } from "@/stores/notification-store";
7
+ import {
8
+ ContextMenu,
9
+ ContextMenuContent,
10
+ ContextMenuItem,
11
+ ContextMenuSeparator,
12
+ ContextMenuTrigger,
13
+ } from "@/components/ui/context-menu";
7
14
 
8
15
  interface DraggableTabProps {
9
16
  tab: Tab;
@@ -23,11 +30,13 @@ interface DraggableTabProps {
23
30
  tabRef: (el: HTMLButtonElement | null) => void;
24
31
  /** If provided, double-clicking the title enters inline rename mode */
25
32
  onRename?: (newTitle: string) => void;
33
+ /** Context menu action handler — receives action name */
34
+ onContextAction?: (action: string) => void;
26
35
  }
27
36
 
28
37
  export function DraggableTab({
29
38
  tab, isActive, icon: Icon, showDropBefore, notificationType, onSelect, onClose,
30
- onDragStart, onDragOver, onDragEnd, onTouchStart, onTouchMove, onTouchEnd, tabRef, onRename,
39
+ onDragStart, onDragOver, onDragEnd, onTouchStart, onTouchMove, onTouchEnd, tabRef, onRename, onContextAction,
31
40
  }: DraggableTabProps) {
32
41
  const [editing, setEditing] = useState(false);
33
42
  const [editValue, setEditValue] = useState(tab.title);
@@ -56,76 +65,120 @@ export function DraggableTab({
56
65
  }
57
66
  : undefined;
58
67
 
68
+ const isFile = tab.type === "editor";
69
+
70
+ const tabButton = (
71
+ <button
72
+ ref={tabRef}
73
+ data-tab-item
74
+ draggable={!editing}
75
+ onClick={onSelect}
76
+ onAuxClick={(e) => { if (e.button === 1 && tab.closable) { e.preventDefault(); onClose(); } }}
77
+ onDragStart={onDragStart}
78
+ onDragOver={onDragOver}
79
+ onDragEnd={onDragEnd}
80
+ onTouchStart={onTouchStart}
81
+ onTouchMove={onTouchMove}
82
+ onTouchEnd={onTouchEnd}
83
+ style={colorStyle}
84
+ className={cn(
85
+ "group flex items-center gap-1 px-3 h-10 whitespace-nowrap text-xs transition-colors",
86
+ "border-b-2 -mb-px cursor-grab active:cursor-grabbing",
87
+ !colorStyle && (isActive
88
+ ? "border-primary text-primary"
89
+ : "border-transparent text-text-secondary hover:text-foreground"),
90
+ colorStyle && "border-transparent",
91
+ )}
92
+ >
93
+ <span className="relative">
94
+ <Icon className="size-4" />
95
+ {notificationType && !isActive && (
96
+ <span className={cn("absolute -top-1 -right-1 size-2 rounded-full", notificationColor(notificationType))} />
97
+ )}
98
+ </span>
99
+ {editing ? (
100
+ <input
101
+ ref={inputRef}
102
+ value={editValue}
103
+ onChange={(e) => setEditValue(e.target.value)}
104
+ onBlur={commitRename}
105
+ onKeyDown={(e) => {
106
+ if (e.key === "Enter") commitRename();
107
+ if (e.key === "Escape") setEditing(false);
108
+ e.stopPropagation();
109
+ }}
110
+ onClick={(e) => e.stopPropagation()}
111
+ className="max-w-[120px] bg-surface-elevated text-xs px-1 py-0.5 rounded border border-border outline-none focus:border-primary"
112
+ autoFocus
113
+ />
114
+ ) : (
115
+ <span
116
+ className="max-w-[120px] truncate"
117
+ onDoubleClick={(e) => {
118
+ if (onRename) { e.stopPropagation(); setEditing(true); }
119
+ }}
120
+ >
121
+ {tab.title}
122
+ </span>
123
+ )}
124
+ {tab.closable && !editing && (
125
+ <span
126
+ role="button"
127
+ tabIndex={0}
128
+ onClick={(e) => { e.stopPropagation(); onClose(); }}
129
+ onKeyDown={(e) => { if (e.key === "Enter") { e.stopPropagation(); onClose(); } }}
130
+ className="ml-1 can-hover:opacity-0 can-hover:group-hover:opacity-100 rounded-sm hover:bg-surface-elevated p-0.5 transition-opacity"
131
+ >
132
+ <X className="size-3" />
133
+ </span>
134
+ )}
135
+ </button>
136
+ );
137
+
59
138
  return (
60
139
  <div className="relative flex items-center">
61
140
  {showDropBefore && (
62
141
  <div className="absolute left-0 top-1 bottom-1 w-0.5 bg-primary rounded-full z-10" />
63
142
  )}
64
- <button
65
- ref={tabRef}
66
- data-tab-item
67
- draggable={!editing}
68
- onClick={onSelect}
69
- onAuxClick={(e) => { if (e.button === 1 && tab.closable) { e.preventDefault(); onClose(); } }}
70
- onDragStart={onDragStart}
71
- onDragOver={onDragOver}
72
- onDragEnd={onDragEnd}
73
- onTouchStart={onTouchStart}
74
- onTouchMove={onTouchMove}
75
- onTouchEnd={onTouchEnd}
76
- style={colorStyle}
77
- className={cn(
78
- "group flex items-center gap-1 px-3 h-10 whitespace-nowrap text-xs transition-colors",
79
- "border-b-2 -mb-px cursor-grab active:cursor-grabbing",
80
- !colorStyle && (isActive
81
- ? "border-primary text-primary"
82
- : "border-transparent text-text-secondary hover:text-foreground"),
83
- colorStyle && "border-transparent",
84
- )}
85
- >
86
- <span className="relative">
87
- <Icon className="size-4" />
88
- {notificationType && !isActive && (
89
- <span className={cn("absolute -top-1 -right-1 size-2 rounded-full", notificationColor(notificationType))} />
90
- )}
91
- </span>
92
- {editing ? (
93
- <input
94
- ref={inputRef}
95
- value={editValue}
96
- onChange={(e) => setEditValue(e.target.value)}
97
- onBlur={commitRename}
98
- onKeyDown={(e) => {
99
- if (e.key === "Enter") commitRename();
100
- if (e.key === "Escape") setEditing(false);
101
- e.stopPropagation();
102
- }}
103
- onClick={(e) => e.stopPropagation()}
104
- className="max-w-[120px] bg-surface-elevated text-xs px-1 py-0.5 rounded border border-border outline-none focus:border-primary"
105
- autoFocus
106
- />
107
- ) : (
108
- <span
109
- className="max-w-[120px] truncate"
110
- onDoubleClick={(e) => {
111
- if (onRename) { e.stopPropagation(); setEditing(true); }
112
- }}
113
- >
114
- {tab.title}
115
- </span>
116
- )}
117
- {tab.closable && !editing && (
118
- <span
119
- role="button"
120
- tabIndex={0}
121
- onClick={(e) => { e.stopPropagation(); onClose(); }}
122
- onKeyDown={(e) => { if (e.key === "Enter") { e.stopPropagation(); onClose(); } }}
123
- className="ml-1 can-hover:opacity-0 can-hover:group-hover:opacity-100 rounded-sm hover:bg-surface-elevated p-0.5 transition-opacity"
124
- >
125
- <X className="size-3" />
126
- </span>
127
- )}
128
- </button>
143
+ {onContextAction ? (
144
+ <ContextMenu>
145
+ <ContextMenuTrigger asChild>
146
+ {tabButton}
147
+ </ContextMenuTrigger>
148
+ <ContextMenuContent>
149
+ {isFile && (
150
+ <>
151
+ <ContextMenuItem onClick={() => onContextAction("copy-path")}>
152
+ Copy Path
153
+ </ContextMenuItem>
154
+ <ContextMenuItem onClick={() => onContextAction("download")}>
155
+ <Download className="size-3.5 mr-2" />
156
+ Download
157
+ </ContextMenuItem>
158
+ <ContextMenuSeparator />
159
+ <ContextMenuItem onClick={() => onContextAction("rename")}>
160
+ Rename
161
+ </ContextMenuItem>
162
+ <ContextMenuItem variant="destructive" onClick={() => onContextAction("delete")}>
163
+ Delete
164
+ </ContextMenuItem>
165
+ <ContextMenuSeparator />
166
+ </>
167
+ )}
168
+ {tab.closable && (
169
+ <ContextMenuItem onClick={() => onContextAction("close")}>
170
+ Close
171
+ </ContextMenuItem>
172
+ )}
173
+ <ContextMenuItem onClick={() => onContextAction("close-others")}>
174
+ Close Others
175
+ </ContextMenuItem>
176
+ <ContextMenuItem onClick={() => onContextAction("close-right")}>
177
+ Close to the Right
178
+ </ContextMenuItem>
179
+ </ContextMenuContent>
180
+ </ContextMenu>
181
+ ) : tabButton}
129
182
  </div>
130
183
  );
131
184
  }
@@ -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(