@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
@@ -1,11 +1,12 @@
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
- ChevronRight, Globe, Puzzle,
5
+ ChevronRight, Globe, Puzzle, Copy, Download, Pencil, Trash2,
6
6
  } from "lucide-react";
7
7
  import { usePanelStore } from "@/stores/panel-store";
8
8
  import { useProjectStore, resolveOrder } from "@/stores/project-store";
9
+ import { useFileStore, type FileNode } from "@/stores/file-store";
9
10
  import { findPanelPosition, MAX_ROWS } from "@/stores/panel-utils";
10
11
  import { resolveProjectColor } from "@/lib/project-palette";
11
12
  import { getProjectInitials } from "@/lib/project-avatar";
@@ -14,18 +15,20 @@ import { cn } from "@/lib/utils";
14
15
  import { openCommandPalette } from "@/hooks/use-global-keybindings";
15
16
  import { useNotificationStore, notificationColor } from "@/stores/notification-store";
16
17
  import { useTabOverflow, getHiddenUnreadDirection } from "@/hooks/use-tab-overflow";
18
+ import { downloadFile } from "@/lib/file-download";
19
+ import { FileActions } from "@/components/explorer/file-actions";
17
20
 
18
21
  const NEW_TAB_OPTIONS: { type: TabType; label: string }[] = [
19
22
  { type: "terminal", label: "Terminal" },
20
23
  { type: "chat", label: "AI Chat" },
21
- { type: "git-graph", label: "Git Graph" },
22
24
  { type: "settings", label: "Settings" },
23
25
  ];
24
26
  const NEW_TAB_LABELS: Partial<Record<TabType, string>> = Object.fromEntries(NEW_TAB_OPTIONS.map((o) => [o.type, o.label]));
25
27
 
26
28
  const TAB_ICONS: Record<TabType, React.ElementType> = {
27
29
  terminal: Terminal, chat: MessageSquare, editor: FileCode, database: Database, sqlite: Database, postgres: Database,
28
- "git-graph": GitBranch, "git-diff": FileDiff, settings: Settings, ports: Globe,
30
+ "git-diff": FileDiff, settings: Settings, ports: Globe,
31
+ extension: Puzzle,
29
32
  "extension-webview": Puzzle,
30
33
  };
31
34
 
@@ -112,11 +115,33 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
112
115
  usePanelStore.getState().moveTab(tabId, pid, targetPanelId);
113
116
  }
114
117
 
118
+ const [fileActionState, setFileActionState] = useState<{ action: string; node: FileNode; tabId: string } | null>(null);
119
+
120
+ function handleFileAction(tab: Tab, action: string) {
121
+ const filePath = tab.metadata?.filePath as string | undefined;
122
+ const projectName = tab.metadata?.projectName as string | undefined;
123
+ switch (action) {
124
+ case "copy-path":
125
+ if (filePath) navigator.clipboard.writeText(filePath).catch(() => {});
126
+ break;
127
+ case "download":
128
+ if (filePath && projectName) downloadFile(projectName, filePath);
129
+ break;
130
+ case "rename":
131
+ case "delete":
132
+ if (filePath) {
133
+ setFileActionState({ action, tabId: tab.id, node: { name: tab.title, path: filePath, type: "file" } });
134
+ }
135
+ break;
136
+ }
137
+ setMenuTabId(null);
138
+ }
139
+
115
140
  const { activeProject: activeProjectForTab } = useProjectStore.getState();
116
141
  function handleNewTab(type: TabType) {
117
142
  const state = usePanelStore.getState();
118
143
  const firstPanelId = state.grid[0]?.[0] ?? state.focusedPanelId;
119
- const needsProject = type === "git-graph" || type === "git-diff" || type === "terminal" || type === "chat";
144
+ const needsProject = type === "git-diff" || type === "terminal" || type === "chat";
120
145
  const metadata = needsProject ? { projectName: activeProjectForTab?.name } : undefined;
121
146
  state.openTab(
122
147
  { type, title: NEW_TAB_LABELS[type] ?? type, metadata, projectId: activeProjectForTab?.name ?? null, closable: true },
@@ -186,7 +211,7 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
186
211
  <div className="flex-1 min-w-0 relative flex items-center h-12 -ml-4">
187
212
  <div ref={mobileScrollRef} className="flex-1 min-w-0 flex items-center h-12 overflow-x-auto scrollbar-none pl-4">
188
213
  {tabs.map((tab) => {
189
- const Icon = TAB_ICONS[tab.type];
214
+ const Icon = TAB_ICONS[tab.type] || Puzzle;
190
215
  const isActive = tab.id === activeTabId;
191
216
  const sessionId = tab.type === "chat" ? (tab.metadata?.sessionId as string) : undefined;
192
217
  const entry = sessionId ? notifications.get(sessionId) : undefined;
@@ -269,13 +294,40 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
269
294
  <div className="px-3 py-2 text-xs text-text-secondary border-b border-border truncate">
270
295
  {menuTab.title}
271
296
  </div>
297
+ {menuTab.type === "editor" && (
298
+ <>
299
+ <button onClick={() => handleFileAction(menuTab, "copy-path")}
300
+ className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
301
+ <Copy className="size-4" /> Copy Path
302
+ </button>
303
+ <button onClick={() => handleFileAction(menuTab, "download")}
304
+ className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
305
+ <Download className="size-4" /> Download
306
+ </button>
307
+ <button onClick={() => handleFileAction(menuTab, "rename")}
308
+ className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
309
+ <Pencil className="size-4" /> Rename
310
+ </button>
311
+ <button onClick={() => handleFileAction(menuTab, "delete")}
312
+ className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-error active:bg-surface-elevated">
313
+ <Trash2 className="size-4" /> Delete
314
+ </button>
315
+ <div className="h-px bg-border mx-2" />
316
+ </>
317
+ )}
318
+ {menuTab.closable && (
319
+ <button onClick={() => { usePanelStore.getState().closeTab(menuTabId!); setMenuTabId(null); }}
320
+ className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
321
+ <X className="size-4" /> Close
322
+ </button>
323
+ )}
272
324
  {menuTabIdx > 0 && (
273
325
  <button onClick={() => { moveTabLeft(menuTabId!); setMenuTabId(null); }}
274
326
  className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
275
327
  <ArrowLeft className="size-4" /> Move Left
276
328
  </button>
277
329
  )}
278
- {menuTabIdx < tabs.length - 1 && (
330
+ {menuTabIdx < menuTabPanelTabs.length - 1 && (
279
331
  <button onClick={() => { moveTabRight(menuTabId!); setMenuTabId(null); }}
280
332
  className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
281
333
  <ArrowRight className="size-4" /> Move Right
@@ -296,6 +348,21 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
296
348
  </div>
297
349
  </>
298
350
  )}
351
+
352
+ {fileActionState && (
353
+ <FileActions
354
+ action={fileActionState.action}
355
+ node={fileActionState.node}
356
+ projectName={activeProjectForTab?.name ?? ""}
357
+ onClose={() => setFileActionState(null)}
358
+ onRefresh={() => {
359
+ if (activeProjectForTab) useFileStore.getState().fetchTree(activeProjectForTab.name);
360
+ if (fileActionState.action === "delete") {
361
+ usePanelStore.getState().closeTab(fileActionState.tabId);
362
+ }
363
+ }}
364
+ />
365
+ )}
299
366
  </nav>
300
367
  );
301
368
  }
@@ -1,9 +1,8 @@
1
- import { useEffect, useRef, useCallback } from "react";
1
+ import { useEffect, useRef, useCallback, useState } from "react";
2
2
  import {
3
3
  Plus,
4
4
  Terminal,
5
5
  MessageSquare,
6
- GitBranch,
7
6
  FileDiff,
8
7
  Settings,
9
8
  FileCode,
@@ -16,6 +15,7 @@ import {
16
15
  import { useTabStore, type TabType } from "@/stores/tab-store";
17
16
  import { usePanelStore } from "@/stores/panel-store";
18
17
  import { useProjectStore } from "@/stores/project-store";
18
+ import { useFileStore, type FileNode } from "@/stores/file-store";
19
19
  import { useTabDrag } from "@/hooks/use-tab-drag";
20
20
  import { useTouchTabDrag, wasTouchDragRecent } from "@/hooks/use-touch-tab-drag";
21
21
  import { openCommandPalette } from "@/hooks/use-global-keybindings";
@@ -25,6 +25,8 @@ import { useTabOverflow, getHiddenUnreadDirection } from "@/hooks/use-tab-overfl
25
25
  import { DraggableTab } from "./draggable-tab";
26
26
  import { cn } from "@/lib/utils";
27
27
  import type { Tab } from "@/stores/tab-store";
28
+ import { downloadFile } from "@/lib/file-download";
29
+ import { FileActions } from "@/components/explorer/file-actions";
28
30
 
29
31
  const TAB_ICONS: Record<TabType, React.ElementType> = {
30
32
  terminal: Terminal,
@@ -33,10 +35,10 @@ const TAB_ICONS: Record<TabType, React.ElementType> = {
33
35
  database: Database,
34
36
  sqlite: Database,
35
37
  postgres: Database,
36
- "git-graph": GitBranch,
37
38
  "git-diff": FileDiff,
38
39
  settings: Settings,
39
40
  ports: Globe,
41
+ extension: Puzzle,
40
42
  "extension-webview": Puzzle,
41
43
  };
42
44
 
@@ -86,6 +88,56 @@ export function TabBar({ panelId }: TabBarProps) {
86
88
  }
87
89
  }, []);
88
90
 
91
+ // File action dialog state for tab context menu (rename/delete)
92
+ const [fileActionState, setFileActionState] = useState<{ action: string; node: FileNode; tabId: string } | null>(null);
93
+
94
+ /** Handle context menu actions on a tab */
95
+ const handleTabContextAction = useCallback((tab: Tab, action: string) => {
96
+ const panelState = usePanelStore.getState();
97
+ const pTabs = panelState.panels[effectivePanelId]?.tabs ?? [];
98
+
99
+ switch (action) {
100
+ case "close":
101
+ panelState.closeTab(tab.id, effectivePanelId);
102
+ break;
103
+ case "close-others":
104
+ for (const t of pTabs) {
105
+ if (t.id !== tab.id && t.closable) panelState.closeTab(t.id, effectivePanelId);
106
+ }
107
+ break;
108
+ case "close-right": {
109
+ const idx = pTabs.findIndex((t) => t.id === tab.id);
110
+ for (let i = idx + 1; i < pTabs.length; i++) {
111
+ if (pTabs[i]!.closable) panelState.closeTab(pTabs[i]!.id, effectivePanelId);
112
+ }
113
+ break;
114
+ }
115
+ case "copy-path": {
116
+ const filePath = tab.metadata?.filePath as string | undefined;
117
+ if (filePath) navigator.clipboard.writeText(filePath).catch(() => {});
118
+ break;
119
+ }
120
+ case "download": {
121
+ const filePath = tab.metadata?.filePath as string | undefined;
122
+ const projectName = tab.metadata?.projectName as string | undefined;
123
+ if (filePath && projectName) downloadFile(projectName, filePath);
124
+ break;
125
+ }
126
+ case "rename":
127
+ case "delete": {
128
+ const filePath = tab.metadata?.filePath as string | undefined;
129
+ if (filePath) {
130
+ setFileActionState({
131
+ action,
132
+ tabId: tab.id,
133
+ node: { name: tab.title, path: filePath, type: "file" },
134
+ });
135
+ }
136
+ break;
137
+ }
138
+ }
139
+ }, [effectivePanelId]);
140
+
89
141
  /** Double-click on empty bar area → open command palette */
90
142
  function handleBarDoubleClick(e: React.MouseEvent) {
91
143
  // Only trigger if clicking directly on the bar or scroll container (not on a tab)
@@ -103,6 +155,7 @@ export function TabBar({ panelId }: TabBarProps) {
103
155
  }
104
156
 
105
157
  return (
158
+ <>
106
159
  <div
107
160
  className="hidden md:flex items-center h-10 border-b border-border bg-background relative"
108
161
  onDragOver={handleDragOverBar}
@@ -140,7 +193,7 @@ export function TabBar({ panelId }: TabBarProps) {
140
193
  key={tab.id}
141
194
  tab={tab}
142
195
  isActive={tab.id === activeTabId}
143
- icon={TAB_ICONS[tab.type]}
196
+ icon={TAB_ICONS[tab.type] || Puzzle}
144
197
  showDropBefore={dropIndex === i}
145
198
  notificationType={notiType}
146
199
  onSelect={() => {
@@ -160,6 +213,7 @@ export function TabBar({ panelId }: TabBarProps) {
160
213
  else tabRefs.current.delete(tab.id);
161
214
  }}
162
215
  onRename={tab.type === "chat" ? (title) => handleRenameTab(tab, title) : undefined}
216
+ onContextAction={(action) => handleTabContextAction(tab, action)}
163
217
  />
164
218
  );
165
219
  })}
@@ -194,5 +248,23 @@ export function TabBar({ panelId }: TabBarProps) {
194
248
  </button>
195
249
  )}
196
250
  </div>
251
+
252
+ {fileActionState && (
253
+ <FileActions
254
+ action={fileActionState.action}
255
+ node={fileActionState.node}
256
+ projectName={activeProject?.name ?? ""}
257
+ onClose={() => setFileActionState(null)}
258
+ onRefresh={() => {
259
+ if (activeProject) useFileStore.getState().fetchTree(activeProject.name);
260
+ // Close tab after file deletion (onRefresh only called on success)
261
+ if (fileActionState.action === "delete") {
262
+ usePanelStore.getState().closeTab(fileActionState.tabId, effectivePanelId);
263
+ }
264
+ }}
265
+ />
266
+ )}
267
+ </>
197
268
  );
198
269
  }
270
+
@@ -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}
@@ -96,6 +96,9 @@ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
96
96
  // No supervisor — manual restart needed
97
97
  toast.info(data.message || "Upgrade installed. Restart PPM manually.");
98
98
  setUpgrading(false);
99
+ if (availableVersion) {
100
+ sessionStorage.setItem(DISMISS_KEY_PREFIX + availableVersion, "1");
101
+ }
99
102
  setDismissed(true);
100
103
  }
101
104
  } catch (e) {
@@ -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
  }
@@ -0,0 +1,142 @@
1
+ import { useState, useEffect, useRef, type ReactNode } from "react";
2
+ import mermaid from "mermaid";
3
+ import { useMdContext, FILE_EXT_RE, GLOB_CHARS_RE } from "./markdown-context";
4
+ import { useTabStore } from "@/stores/tab-store";
5
+
6
+ const MERMAID_KEYWORDS = /^(sequenceDiagram|flowchart|graph\s|classDiagram|stateDiagram|erDiagram|journey|gantt|pie|quadrantChart|requirementDiagram|gitGraph|mindmap|timeline|sankey|xychart|block-beta|packet-beta|architecture-beta|kanban)\b/;
7
+
8
+ let mermaidInitialized = false;
9
+ function ensureMermaidInit() {
10
+ if (mermaidInitialized) return;
11
+ mermaid.initialize({ startOnLoad: false, theme: "default", securityLevel: "loose", fontFamily: "ui-sans-serif, system-ui, sans-serif" });
12
+ mermaidInitialized = true;
13
+ }
14
+
15
+ /** Extract plain text from a hast node tree */
16
+ function hastToText(node: any): string {
17
+ if (!node) return "";
18
+ if (node.type === "text") return node.value ?? "";
19
+ if (node.children) return node.children.map(hastToText).join("");
20
+ return "";
21
+ }
22
+
23
+ /** Pre — code block wrapper with mermaid detection and action buttons */
24
+ export function MdPre({ children, node, ...rest }: any) {
25
+ const { codeActions, projectName, openDiagramOverlay } = useMdContext();
26
+ const openTab = useTabStore((s) => s.openTab);
27
+
28
+ const codeNode = node?.children?.[0];
29
+ const langClass = (codeNode?.properties?.className ?? []).find((c: string) => c.startsWith("language-"));
30
+ const lang = langClass?.replace("language-", "");
31
+ const text = hastToText(codeNode);
32
+
33
+ // Mermaid detection
34
+ if (lang === "mermaid" || (!lang && MERMAID_KEYWORDS.test(text.trim()))) {
35
+ return <MermaidDiagram source={text.trim()} />;
36
+ }
37
+
38
+ const isBash = /^(bash|sh|shell|zsh)$/.test(lang || "") || (!lang && text.startsWith("$"));
39
+
40
+ return (
41
+ <pre {...rest} className={`relative group ${rest.className || ""}`}>
42
+ {children}
43
+ {codeActions && (
44
+ <div className="code-actions absolute top-1 right-1 flex gap-1">
45
+ <ActionBtn title="Copy" icon={<CopyIcon />} activeIcon={<CheckIcon />} onClick={() => navigator.clipboard.writeText(text)} />
46
+ {isBash && projectName && (
47
+ <ActionBtn
48
+ title="Run in terminal"
49
+ icon={<PlayIcon />}
50
+ onClick={() => {
51
+ navigator.clipboard.writeText(text.replace(/^\$\s*/gm, ""));
52
+ openTab({ type: "terminal", title: "Terminal", metadata: { projectName }, projectId: projectName, closable: true });
53
+ }}
54
+ />
55
+ )}
56
+ </div>
57
+ )}
58
+ </pre>
59
+ );
60
+ }
61
+
62
+ /** Code — inline code with file clicking; block code passes through */
63
+ export function MdCode({ className, children, node, ...rest }: any) {
64
+ const { openFileOrSearch } = useMdContext();
65
+
66
+ // Block code (has language/hljs class from rehype-highlight) — render as-is
67
+ if (className) return <code className={className} {...rest}>{children}</code>;
68
+
69
+ // Inline code — check for clickable file paths
70
+ const text = String(children ?? "").trim();
71
+ if (text && !text.includes(" ") && !GLOB_CHARS_RE.test(text) && FILE_EXT_RE.test(text)) {
72
+ return (
73
+ <code
74
+ onClick={() => openFileOrSearch(text)}
75
+ style={{ cursor: "pointer", textDecoration: "underline", textDecorationStyle: "dotted" as const }}
76
+ {...rest}
77
+ >
78
+ {children}
79
+ </code>
80
+ );
81
+ }
82
+
83
+ return <code {...rest}>{children}</code>;
84
+ }
85
+
86
+ /** Mermaid diagram renderer with click-to-expand */
87
+ function MermaidDiagram({ source }: { source: string }) {
88
+ const { openDiagramOverlay } = useMdContext();
89
+ const [svg, setSvg] = useState<string | null>(null);
90
+
91
+ useEffect(() => {
92
+ ensureMermaidInit();
93
+ const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
94
+ mermaid.render(id, source).then(({ svg }) => setSvg(svg)).catch(() => {});
95
+ }, [source]);
96
+
97
+ if (!svg) return <pre><code>{source}</code></pre>;
98
+
99
+ return (
100
+ <div
101
+ className="mermaid-diagram group relative cursor-pointer rounded-lg border border-border bg-white dark:bg-zinc-50 p-3 overflow-x-auto my-2"
102
+ onClick={() => openDiagramOverlay(svg)}
103
+ >
104
+ <div dangerouslySetInnerHTML={{ __html: svg }} />
105
+ <div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 rounded bg-black/60 text-white text-xs can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity pointer-events-none">
106
+ Click to expand
107
+ </div>
108
+ </div>
109
+ );
110
+ }
111
+
112
+ /** Reusable code-block action button with optional active state */
113
+ function ActionBtn({ title, icon, activeIcon, onClick }: { title: string; icon: ReactNode; activeIcon?: ReactNode; onClick: () => void }) {
114
+ const [active, setActive] = useState(false);
115
+ return (
116
+ <button
117
+ className="flex items-center justify-center size-6 rounded bg-surface-elevated/80 hover:bg-surface-elevated text-text-secondary hover:text-text-primary transition-colors border border-border/50"
118
+ title={title}
119
+ onClick={() => { onClick(); if (activeIcon) { setActive(true); setTimeout(() => setActive(false), 2000); } }}
120
+ >
121
+ {active && activeIcon ? activeIcon : icon}
122
+ </button>
123
+ );
124
+ }
125
+
126
+ const CopyIcon = () => (
127
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
128
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
129
+ </svg>
130
+ );
131
+
132
+ const CheckIcon = () => (
133
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
134
+ <polyline points="20 6 9 17 4 12" />
135
+ </svg>
136
+ );
137
+
138
+ const PlayIcon = () => (
139
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
140
+ <polyline points="4 17 10 11 4 5" /><line x1="12" y1="19" x2="20" y2="19" />
141
+ </svg>
142
+ );
@@ -0,0 +1,20 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /** Common text file extensions that PPM can open as editor tabs */
4
+ const FILE_EXTS = "ts|tsx|js|jsx|mjs|cjs|py|json|md|mdx|yaml|yml|toml|css|scss|less|html|htm|sh|bash|zsh|go|rs|sql|rb|java|kt|swift|c|cpp|h|hpp|cs|vue|svelte|txt|env|cfg|conf|ini|xml|csv|log|dockerfile|makefile|gradle";
5
+ export const FILE_EXT_RE = new RegExp(`\\.(${FILE_EXTS})$`, "i");
6
+ /** Glob/regex chars that indicate a pattern, not a real file */
7
+ export const GLOB_CHARS_RE = /[*?{}\[\]]/;
8
+ /** Detect local absolute file paths (Unix or Windows) */
9
+ export const LOCAL_PATH_RE = /^(\/|[A-Za-z]:[/\\])/;
10
+
11
+ export interface MdContextValue {
12
+ projectName?: string;
13
+ codeActions: boolean;
14
+ openFileOrSearch: (path: string) => void;
15
+ openImageOverlay: (url: string, alt: string) => void;
16
+ openDiagramOverlay: (svg: string) => void;
17
+ }
18
+
19
+ export const MdContext = createContext<MdContextValue>(null!);
20
+ export const useMdContext = () => useContext(MdContext);