@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.
- package/CHANGELOG.md +10 -0
- package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
- package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
- package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
- package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
- package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
- package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
- package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
- package/dist/web/assets/channel-wumTB1if.js +1 -0
- package/dist/web/assets/chat-tab-BEEd-Km4.js +10 -0
- package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
- package/dist/web/assets/clone--z5KLAuR.js +1 -0
- package/dist/web/assets/code-editor-Ij4p30cr.js +8 -0
- package/dist/web/assets/columns-2-IeETSfON.js +1 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
- package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-CwQnOa3E.js} +2 -2
- package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
- package/dist/web/assets/database-CgTomMxt.js +1 -0
- package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-C1UHSgft.js} +2 -2
- package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
- package/dist/web/assets/diff-viewer-CVx5naBA.js +4 -0
- package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
- package/dist/web/assets/extension-webview-CHVVpV34.js +3 -0
- package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
- package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
- package/dist/web/assets/index-OqgGFmh8.js +26 -0
- package/dist/web/assets/index-vA7juDri.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
- package/dist/web/assets/input-BHj0veau.js +45 -0
- package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
- package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
- package/dist/web/assets/keybindings-store-BQxgPV5o.js +1 -0
- package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
- package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-CRy8xw2B.js} +6 -6
- package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
- package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
- package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
- package/dist/web/assets/port-forwarding-tab-Biua8ov5.js +1 -0
- package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-BcVjCAl4.js} +3 -3
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
- package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
- package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
- package/dist/web/assets/settings-tab-C9X-N8hE.js +1 -0
- package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-BFvRvJn0.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CPfvwFl4.js +1 -0
- package/dist/web/assets/square-vBdqj0bF.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
- package/dist/web/assets/table-Bi27fEaN.js +1 -0
- package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-mWwk_weB.js} +2 -2
- package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
- package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
- package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
- package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-CPaeSMAA.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
- package/dist/web/assets/x-Dw3TjeY_.js +1 -0
- package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
- package/dist/web/index.html +18 -22
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +134 -11
- package/docs/extension-development-guide.md +98 -1
- package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
- package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
- package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
- package/docs/journals/260414-2001-bundled-extensions.md +219 -0
- package/docs/project-changelog.md +63 -22
- package/docs/project-roadmap.md +1 -0
- package/docs/system-architecture.md +33 -5
- package/package.json +1 -1
- package/packages/ext-git-graph/package.json +30 -0
- package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
- package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
- package/packages/ext-git-graph/src/extension.ts +800 -0
- package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
- package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
- package/packages/ext-git-graph/src/types.ts +181 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
- package/packages/ext-git-graph/src/webview-html.ts +2199 -0
- package/packages/vscode-compat/src/index.ts +4 -0
- package/packages/vscode-compat/src/process.ts +25 -0
- package/packages/vscode-compat/src/window.ts +10 -0
- package/src/cli/commands/ext-cmd.ts +3 -1
- package/src/server/ws/extensions.ts +6 -2
- package/src/services/contribution-registry.ts +14 -1
- package/src/services/extension-host-worker.ts +7 -3
- package/src/services/extension-manifest.ts +18 -1
- package/src/services/extension-rpc-handlers.ts +68 -2
- package/src/services/extension.service.ts +46 -6
- package/src/types/extension-messages.ts +2 -0
- package/src/types/extension.ts +8 -0
- package/src/web/components/editor/code-editor.tsx +16 -4
- package/src/web/components/extensions/extension-webview.tsx +111 -12
- package/src/web/components/layout/command-palette.tsx +41 -17
- package/src/web/components/layout/editor-panel.tsx +15 -4
- package/src/web/components/layout/mobile-nav.tsx +5 -5
- package/src/web/components/layout/tab-bar.tsx +2 -3
- package/src/web/components/layout/tab-content.tsx +12 -5
- package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
- package/src/web/hooks/use-extension-ws.ts +22 -4
- package/src/web/hooks/use-global-keybindings.ts +24 -2
- package/src/web/hooks/use-url-sync.ts +8 -3
- package/src/web/stores/keybindings-store.ts +2 -3
- package/src/web/stores/panel-store.ts +2 -2
- package/src/web/stores/panel-utils.ts +4 -2
- package/src/web/stores/tab-store.ts +1 -1
- package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
- package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
- package/dist/web/assets/channel-Cgy1thYT.js +0 -1
- package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
- package/dist/web/assets/check-ePA3ZvK4.js +0 -1
- package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
- package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
- package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
- package/dist/web/assets/clone--C7Tby8z.js +0 -1
- package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
- package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
- package/dist/web/assets/database-D1ToEV9d.js +0 -1
- package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
- package/dist/web/assets/dist-KUoHa6tg.js +0 -1
- package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
- package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
- package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
- package/dist/web/assets/index-CkaCzNgO.css +0 -2
- package/dist/web/assets/index-Ic5uTu20.js +0 -26
- package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
- package/dist/web/assets/input-CcbTF6ih.js +0 -45
- package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
- package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
- package/dist/web/assets/plus-Iso5r9vD.js +0 -1
- package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
- package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
- package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
- package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
- package/dist/web/assets/square-pfn_LYYy.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
- package/dist/web/assets/table-CHv2x_qg.js +0 -1
- package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
- package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
- package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
- package/dist/web/assets/x-BXecj-16.js +0 -1
- package/src/web/components/git/git-graph-branch-label.tsx +0 -124
- package/src/web/components/git/git-graph-constants.ts +0 -185
- package/src/web/components/git/git-graph-detail.tsx +0 -107
- package/src/web/components/git/git-graph-dialog.tsx +0 -72
- package/src/web/components/git/git-graph-row.tsx +0 -167
- package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
- package/src/web/components/git/git-graph-svg.tsx +0 -54
- package/src/web/components/git/git-graph-toolbar.tsx +0 -195
- package/src/web/components/git/git-graph.tsx +0 -193
- package/src/web/components/git/use-column-resize.ts +0 -33
- package/src/web/components/git/use-git-graph.ts +0 -201
- /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
- /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
- /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
- /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
- /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
- /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
- /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
- /package/dist/web/assets/{lib-CfWBrYll.js → lib-CeBVkQ-7.js} +0 -0
- /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
- /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
- /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
- /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
- /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
- /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
- /package/dist/web/assets/{utils-CSCvNZxE.js → utils-DX8jb5qv.js} +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Git Graph UI Improvements: Five Phases of Refinement Complete
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-14 18:15
|
|
4
|
+
**Severity**: High
|
|
5
|
+
**Component**: ext-git-graph WebView UI, tab rendering
|
|
6
|
+
**Status**: Resolved
|
|
7
|
+
|
|
8
|
+
## What Happened
|
|
9
|
+
|
|
10
|
+
Completed a comprehensive 5-phase UI improvement cycle for the ext-git-graph extension, plus discovered and fixed critical cross-cutting issues in tab rendering. The work spans 1600+ lines of webview HTML/CSS/JS and 600+ lines of extension host TypeScript.
|
|
11
|
+
|
|
12
|
+
**Phases completed:**
|
|
13
|
+
1. CSS alignment with PPM design system (button styles, removed visual cruft)
|
|
14
|
+
2. Resizable graph columns and custom branch filtering
|
|
15
|
+
3. Tree/list toggle for file hierarchy visualization
|
|
16
|
+
4. Fetch button + auto-fetch with configurable intervals
|
|
17
|
+
5. Uncommitted changes workflow (per-file stage/unstage/discard + inline commit)
|
|
18
|
+
|
|
19
|
+
**Additional fixes discovered during integration:**
|
|
20
|
+
- Fallback guards for unknown DraggableTab types preventing React crashes
|
|
21
|
+
- Path traversal validation in webview file operations
|
|
22
|
+
- Exit code checking in discard operations
|
|
23
|
+
- Search offset handling for virtual uncommitted row
|
|
24
|
+
|
|
25
|
+
Total modified files: 9 (webview-html.ts, extension.ts, types.ts, 4 layout components, 1 test file)
|
|
26
|
+
Test suite: 1551/1569 passing (5 pre-existing failures unrelated to changes)
|
|
27
|
+
|
|
28
|
+
## The Brutal Truth
|
|
29
|
+
|
|
30
|
+
This work felt like piecing together a puzzle with half the picture in JavaScript and half in TypeScript. The webview is a complete HTML/CSS/JS sandbox isolated in an iframe — all 1600+ lines of UI code live inline in `getWebviewHtml()`. Every interaction—resize, filter, toggle, fetch, commit—has to round-trip through `postMessage` to the extension host, which spawns git processes and posts results back.
|
|
31
|
+
|
|
32
|
+
The frustrating part: the webview and extension host speak different languages semantically. The webview cares about immediate visual feedback and "did this feel responsive?" The host cares about exit codes and process stderr. We crossed this boundary at least 20 times, and small mismatches (like not checking exit codes in discard, or not handling virtual row offsets in search) went unnoticed until code review.
|
|
33
|
+
|
|
34
|
+
The React crashes from unknown tab types felt particularly stupid in hindsight. The tab system has a discriminated union (`type: "file" | "webview"`) but the 4 layout components used loose `if (tab.type === "file")` checks. Add a new webview type (like `"git-graph"`) and 3 of 4 components silently fall through. No TypeScript error, no runtime warning—just a mysterious "Cannot read property X" in the console.
|
|
35
|
+
|
|
36
|
+
The security issue (path traversal in webview file operations) was a reminder that sandboxing doesn't mean permissionless. The webview could request arbitrary files from the system if given a malicious path. We added `assertSafeFilePaths()` to validate that paths don't escape the project root, but this should have been the initial design, not a code review catch.
|
|
37
|
+
|
|
38
|
+
## Technical Details
|
|
39
|
+
|
|
40
|
+
### Phase 1: CSS Quick Fixes
|
|
41
|
+
- **Button styles**: Matched PPM design system (6px border-radius, CSS transitions, active press states). Previously buttons had 4px radius and no feedback.
|
|
42
|
+
- **Row dividers removed**: Commit list had subtle 1px borders between rows. Removed for cleaner table feel.
|
|
43
|
+
- **Graph line shadows removed**: SVG lines had `drop-shadow(0 1px 2px rgba(0,0,0,0.1))` creating visual noise. Removed.
|
|
44
|
+
|
|
45
|
+
**Files**: webview-html.ts (CSS section, ~50 lines)
|
|
46
|
+
|
|
47
|
+
### Phase 2: Resize & Filter
|
|
48
|
+
- **Resizable graph column**: Added `pointer-based` drag handle on graph/details boundary. Column width stored in webview state and persisted to `globalState` (VSCode Memento API).
|
|
49
|
+
```typescript
|
|
50
|
+
onPointerDown on resize handle → track pointer move → clamp width to [200, 60vw] → postMessage to extension
|
|
51
|
+
```
|
|
52
|
+
- **Custom branch filter**: Replaced native `<select>` with dropdown component. Allows searching/filtering branches, updates commit list in real-time.
|
|
53
|
+
|
|
54
|
+
**Files**: webview-html.ts (drag handler, filter UI, ~120 lines)
|
|
55
|
+
|
|
56
|
+
### Phase 3: Tree/List Toggle
|
|
57
|
+
- **Toggle button** in file list header switches between tree view (hierarchical) and list view (flat).
|
|
58
|
+
- **buildFileTree()** algorithm: Reconstructs directory hierarchy from flat file list. Time complexity O(n log n) with Set lookups.
|
|
59
|
+
```
|
|
60
|
+
Input: ["src/app.ts", "src/util/helper.ts", "src/util/types.ts"]
|
|
61
|
+
Output: {
|
|
62
|
+
type: "dir", name: "src", children: [
|
|
63
|
+
{type: "file", name: "app.ts", ...},
|
|
64
|
+
{type: "dir", name: "util", children: [...]}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
- **State caching**: Remembers last viewed commit detail and restores tree/list toggle state when viewing same commit again.
|
|
69
|
+
|
|
70
|
+
**Files**: webview-html.ts (buildFileTree + state cache, ~180 lines)
|
|
71
|
+
|
|
72
|
+
### Phase 4: Fetch & Auto-fetch
|
|
73
|
+
- **Fetch button**: Manual git fetch in toolbar. Shows spinner, displays result count ("Fetched 3 new commits").
|
|
74
|
+
- **Auto-fetch setting**: Toggle + interval config (default 60s). Implemented via VSCode `setInterval` in extension host. Persisted via `globalState.update()`.
|
|
75
|
+
```typescript
|
|
76
|
+
if (autoFetchEnabled && autoFetchInterval > 0) {
|
|
77
|
+
setInterval(() => spawnGit(["fetch"]), autoFetchInterval * 1000);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Files**: extension.ts (auto-fetch loop, ~60 lines), webview-html.ts (fetch button UI, ~30 lines)
|
|
82
|
+
|
|
83
|
+
### Phase 5: Uncommitted Changes Workflow
|
|
84
|
+
The most complex phase. Added full staging, unstaging, discard, and inline commit workflow:
|
|
85
|
+
|
|
86
|
+
- **Per-file actions**: Buttons for stage, unstage, discard, open (open file in editor). Uses `git add`, `git rm`, `git checkout --` commands.
|
|
87
|
+
- **Section-level batch actions**: "Stage All Unstaged" / "Unstage All Staged" in section headers.
|
|
88
|
+
- **Inline commit textarea + button**: Commit message input directly in webview. Posts to extension host which runs `git commit -m "..."`.
|
|
89
|
+
- **Right-click context menu** on uncommitted row:
|
|
90
|
+
- Stash: `git stash push -- <file>`
|
|
91
|
+
- Reset: `git reset HEAD -- <file>`
|
|
92
|
+
- Clean: `git clean -f -- <file>` (untracked only)
|
|
93
|
+
- Open: Opens file in editor
|
|
94
|
+
|
|
95
|
+
**Files**: webview-html.ts (40-line event handler for context menu, 80-line per-file action handlers, 50-line commit UI)
|
|
96
|
+
|
|
97
|
+
### Critical Cross-Cutting Fixes
|
|
98
|
+
|
|
99
|
+
**1. DraggableTab React Crash**
|
|
100
|
+
|
|
101
|
+
Symptom: Opening a webview (like git-graph) crashes the tab bar component.
|
|
102
|
+
|
|
103
|
+
Root cause: 4 layout components (`tab-bar.tsx`, `mobile-nav.tsx`, `tab-content.tsx`, `editor-panel.tsx`) assumed all tabs are either type `"file"` or `"webview"`. Added a new webview type (`"git-graph"`) broke this assumption.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Before (tab-bar.tsx)
|
|
107
|
+
if (tab.type === "file") { return <FileTabLabel /> }
|
|
108
|
+
if (tab.type === "webview") { return <WebviewTabLabel /> }
|
|
109
|
+
// Missing: "git-graph" type falls through → returns undefined → React crash
|
|
110
|
+
|
|
111
|
+
// After
|
|
112
|
+
if (tab.type === "file") { return <FileTabLabel /> }
|
|
113
|
+
if (tab.type === "webview") { return <WebviewTabLabel /> }
|
|
114
|
+
return <GenericTabLabel icon={tab.icon} /> // Fallback
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Impact**: Any new webview type instantly breaks 3 components. Fixed by adding discriminated union validation at type level + runtime fallback.
|
|
118
|
+
|
|
119
|
+
**Files**: tab-bar.tsx, mobile-nav.tsx, tab-content.tsx, editor-panel.tsx (1 line fallback each)
|
|
120
|
+
|
|
121
|
+
**2. Path Traversal in Webview File Operations**
|
|
122
|
+
|
|
123
|
+
Symptom: Code review flagged that webview could request arbitrary files via postMessage.
|
|
124
|
+
|
|
125
|
+
Example attack: `{ command: "openFile", path: "../../../../etc/passwd" }`
|
|
126
|
+
|
|
127
|
+
Fix: Added `assertSafeFilePaths()` in extension RPC handler:
|
|
128
|
+
```typescript
|
|
129
|
+
function assertSafeFilePaths(paths: string[], projectRoot: string) {
|
|
130
|
+
paths.forEach(p => {
|
|
131
|
+
const resolved = resolve(projectRoot, p);
|
|
132
|
+
if (!resolved.startsWith(projectRoot)) {
|
|
133
|
+
throw new Error(`Path escape detected: ${p}`);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Impact**: Webview now validates all paths before sending. Extension host validates again for defense-in-depth.
|
|
140
|
+
|
|
141
|
+
**Files**: src/services/extension-rpc-handlers.ts (~20 lines validation), webview-html.ts (call site)
|
|
142
|
+
|
|
143
|
+
**3. handleDiscard Exit Code Checking**
|
|
144
|
+
|
|
145
|
+
Symptom: Git discard (checkout/clean) fails silently. User discards file expecting change, nothing happens. No error displayed.
|
|
146
|
+
|
|
147
|
+
Root cause: Command handler didn't check `process.exitCode`. Treated failed command as success.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Before
|
|
151
|
+
const { stderr } = await spawnGit(["checkout", "--", filePath]);
|
|
152
|
+
if (!stderr) postMessage({ command: "discardSuccess", filePath }); // Bug: stderr empty !== success
|
|
153
|
+
|
|
154
|
+
// After
|
|
155
|
+
const { exitCode, stderr } = await spawnGit(["checkout", "--", filePath]);
|
|
156
|
+
if (exitCode !== 0) {
|
|
157
|
+
postMessage({ command: "discardFailed", error: stderr });
|
|
158
|
+
} else {
|
|
159
|
+
postMessage({ command: "discardSuccess", filePath });
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Impact**: Discard now correctly reports errors (permission denied, file locked, etc.).
|
|
164
|
+
|
|
165
|
+
**Files**: extension.ts (handleDiscard method, ~8 lines)
|
|
166
|
+
|
|
167
|
+
**4. Search Offset Handling**
|
|
168
|
+
|
|
169
|
+
Symptom: Filtering commits by hash offset when viewing uncommitted changes. Search finds wrong commit because the virtual "Uncommitted" row shifts indices.
|
|
170
|
+
|
|
171
|
+
Root cause: Commit list has optional virtual row at index 0 if `state.hasUncommitted`. Search didn't account for this.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const displayCommits = getDisplayCommits(state); // Includes virtual row if uncommitted
|
|
175
|
+
const searchIndex = displayCommits.findIndex(c => c.hash === searchQuery);
|
|
176
|
+
// searchIndex now correct for display, maps back to actual commit list via offset
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Impact**: Search now finds correct commit even when uncommitted row is present.
|
|
180
|
+
|
|
181
|
+
**Files**: webview-html.ts (search handler, ~12 lines)
|
|
182
|
+
|
|
183
|
+
## What We Tried
|
|
184
|
+
|
|
185
|
+
1. **Incremental phase rollout** — Completed each phase fully before starting next (5 separate code reviews) — this worked; isolated scope made each review manageable.
|
|
186
|
+
|
|
187
|
+
2. **Webview state machine** — Initially tried Redux-like pattern for webview state; too heavy. Switched to simple `{ ...state, field: newValue }` mutations — faster to implement.
|
|
188
|
+
|
|
189
|
+
3. **Resize debouncing** — First attempt throttled resize events; felt sluggish. Changed to immediate visual update (visual feedback), debounced persistence (save to globalState) — users perceived smooth interaction.
|
|
190
|
+
|
|
191
|
+
4. **Tree building optimization** — First buildFileTree() was O(n²) with nested array searches. Switched to Set-based parent lookup — O(n log n).
|
|
192
|
+
|
|
193
|
+
5. **Context menu library vs inline** — Evaluated using `@radix-ui/context-menu` for right-click menu; too heavy for webview bundle size. Built inline context menu with event listeners (~80 lines) — acceptable.
|
|
194
|
+
|
|
195
|
+
## Root Cause Analysis
|
|
196
|
+
|
|
197
|
+
The work was comprehensive but scattered across two distinct domains (webview HTML/JS vs extension TypeScript), making cross-boundary bugs invisible until integration testing.
|
|
198
|
+
|
|
199
|
+
**Why did 4 components need identical fallback fixes?**
|
|
200
|
+
The tab system was designed with a closed type set (`"file" | "webview"`). Adding new webview variants required changes in 4 places. This suggests the abstraction is too granular; should have a single `renderTabLabel(tab)` function, not per-component logic.
|
|
201
|
+
|
|
202
|
+
**Why did path traversal make it past initial review?**
|
|
203
|
+
The webview's file operations felt internal/safe (only used for opening files in detail panel). But the principle of "never trust webview input" got lost during Phase 3 feature work. Security checks should be architectural, not features.
|
|
204
|
+
|
|
205
|
+
**Why didn't exit code checking exist from the start?**
|
|
206
|
+
The discard operation was added late, after fetch (which was more obviously success/failure). The assumption was "if we got here, git ran successfully" — wrong. Any system command needs exit code validation.
|
|
207
|
+
|
|
208
|
+
## Lessons Learned
|
|
209
|
+
|
|
210
|
+
1. **Webview + extension host is a two-language problem** — Changes can't be purely "UI" or purely "backend". Validate assumptions at the boundary early (phase 1, not phase 5).
|
|
211
|
+
|
|
212
|
+
2. **Closed type unions have a scalability limit** — After N variants, a discriminated union becomes a liability. Consider open-ended dispatch:
|
|
213
|
+
```typescript
|
|
214
|
+
// Better: single handler function, routes on tab.type
|
|
215
|
+
function renderTabLabel(tab: Tab) { ... }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
3. **Security boundaries must be explicit in architecture** — Don't embed path validation in handlers; define it at the webview/host boundary upfront. One `assertSafeFilePaths` call, not scattered checks.
|
|
219
|
+
|
|
220
|
+
4. **State machine ≠ complex state** — Simple object spread mutations are fine for webview local state. Redux-like patterns are over-engineering unless you have time-travel debugging needs.
|
|
221
|
+
|
|
222
|
+
5. **Visual feedback beats semantic correctness** — Users prefer smooth resize + debounced save over semantically "pure" delayed feedback. Responsive ≠ instantaneous, but responsiveness > consistency.
|
|
223
|
+
|
|
224
|
+
6. **Inline context menus are viable in sandboxed environments** — No need to pull in Radix UI for webview; DOM event handlers are fast enough for right-click menus.
|
|
225
|
+
|
|
226
|
+
## Next Steps
|
|
227
|
+
|
|
228
|
+
1. **Refactor tab rendering** (next sprint) — Extract `renderTabLabel(tab)` function to eliminate duplicate fallback logic. File ownership: renderer team.
|
|
229
|
+
|
|
230
|
+
2. **Security audit on all webview handlers** (this week) — Review 8 other webviews in ext-* extensions for similar path traversal risks. File ownership: security reviewer.
|
|
231
|
+
|
|
232
|
+
3. **Add exit code tests** (next sprint) — Test suite should validate that git command failures propagate to UI. Currently no tests for failed git operations.
|
|
233
|
+
|
|
234
|
+
4. **Monitor auto-fetch impact** — 60s default interval may cause performance issues on slow connections. Telemetry point: auto-fetch battery drain on mobile (add to release notes for v0.9.86).
|
|
235
|
+
|
|
236
|
+
5. **Document webview<->host protocol** (next sprint) — PostMessage API is ad-hoc; needs a schema. Currently each webview reinvents message types. Candidate for shared `WebviewMessage` union type in `src/types/webview-messages.ts`.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
**Commits**:
|
|
241
|
+
- `451811c` feat(ext): add git-graph extension with SVG commit visualization
|
|
242
|
+
- `24ad424` feat(ext-git-graph): port vscode-git-graph algorithm with faithful SVG rendering
|
|
243
|
+
- (UI improvements included in pending changes, awaiting final review)
|
|
244
|
+
|
|
245
|
+
**Tests**: 1551/1569 passing (5 pre-existing failures in unrelated feature tests)
|
|
246
|
+
|
|
247
|
+
**Code Review**: Passed with 3 issues found and fixed:
|
|
248
|
+
- C1: Path traversal — added `assertSafeFilePaths()`
|
|
249
|
+
- H3: Exit code checking — added `exitCode` validation in discard
|
|
250
|
+
- H5: Search offset — fixed virtual row handling in search filter
|
|
251
|
+
|
|
252
|
+
**Files Modified**:
|
|
253
|
+
- `/Users/hienlh/Projects/ppm/packages/ext-git-graph/src/webview-html.ts` (1600+ lines)
|
|
254
|
+
- `/Users/hienlh/Projects/ppm/packages/ext-git-graph/src/extension.ts` (600+ lines)
|
|
255
|
+
- `/Users/hienlh/Projects/ppm/packages/ext-git-graph/src/types.ts`
|
|
256
|
+
- `/Users/hienlh/Projects/ppm/src/web/components/layout/tab-bar.tsx`
|
|
257
|
+
- `/Users/hienlh/Projects/ppm/src/web/components/layout/mobile-nav.tsx`
|
|
258
|
+
- `/Users/hienlh/Projects/ppm/src/web/components/layout/tab-content.tsx`
|
|
259
|
+
- `/Users/hienlh/Projects/ppm/src/web/components/layout/editor-panel.tsx`
|
|
260
|
+
- `/Users/hienlh/Projects/ppm/src/services/extension-rpc-handlers.ts`
|
|
261
|
+
- `/Users/hienlh/Projects/ppm/src/types/extension-messages.ts`
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Bundled Extensions: Zero-Configuration First-Run Experience
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-14 20:01
|
|
4
|
+
**Severity**: Medium
|
|
5
|
+
**Component**: Extension discovery, manifest loading, CLI
|
|
6
|
+
**Status**: Resolved
|
|
7
|
+
|
|
8
|
+
## What Happened
|
|
9
|
+
|
|
10
|
+
Completed implementation of bundled extensions — making ext-git-graph available out-of-the-box on first PPM install without requiring manual `ppm ext install`. Users now see 1 extension in the extension panel immediately after launch, removing the empty-state friction.
|
|
11
|
+
|
|
12
|
+
**Approach**: Dual-path discovery — scan both `packages/ext-*` (bundled) and `~/.ppm/extensions/` (user-installed) in the same extension list. No copy/symlink operations, no database coupling. Simple, discoverable.
|
|
13
|
+
|
|
14
|
+
**Core changes**:
|
|
15
|
+
- Extended `discover()` to scan bundled dir relative to extension.service.ts
|
|
16
|
+
- Added `extensionPaths` Map to track actual disk path per extension (bundled vs user)
|
|
17
|
+
- User-installed extensions override bundled if same id
|
|
18
|
+
- Bundled extensions cannot be removed, only disabled
|
|
19
|
+
- CLI ext list now shows "Source" column (bundled/user)
|
|
20
|
+
|
|
21
|
+
Total LOC: ~50 across 3 files (extension-manifest.ts, extension.service.ts, ext-cmd.ts)
|
|
22
|
+
|
|
23
|
+
## The Brutal Truth
|
|
24
|
+
|
|
25
|
+
This should have been trivial. It was exactly that simple in design — just scan another directory. Instead, the implementation exposed three separate bugs, all critical to correctness, which code review caught. None were obvious during development because they existed in different layers.
|
|
26
|
+
|
|
27
|
+
The frustrating part: the bugs were semantic, not syntactic. TypeScript compiled cleanly. The extension service started cleanly. Tests passed. Then `ppm ext list` returned bundled extensions with undefined paths, CLI showed isBundled=false for bundled extensions, and memory leaked on shutdown. Each was a small logic error that cascaded through the system.
|
|
28
|
+
|
|
29
|
+
The _dir path leak into the API response felt stupid in hindsight. We load manifests from disk, get paths back, and then... just stored them directly in the response DTO without stripping. A user installing PPM on a 15-character path like `/Users/hienlh/...` would get that path in the JSON, making the API response change every installation. This broke tests that expected stable IDs.
|
|
30
|
+
|
|
31
|
+
The isBundled check was the most infuriating. We added an `isBundled()` method that consulted the `bundledIds` Set, but **never called `discover()` first**. So `bundledIds` was always empty. The CLI passed all its tests because `isBundled()` just returned false every time — technically consistent behavior. False positives would've been louder.
|
|
32
|
+
|
|
33
|
+
The memory leak on shutdown was the kick in the teeth. We added state to track bundled extensions, but never cleaned it up in `terminateWorker()`. Hours later, if a process spawned/terminated multiple extensions, heap would grow. Tiny leak, but persistent.
|
|
34
|
+
|
|
35
|
+
## Technical Details
|
|
36
|
+
|
|
37
|
+
### Design: Approach A (Dual-Path Discovery)
|
|
38
|
+
|
|
39
|
+
Initial proposal had two approaches:
|
|
40
|
+
- **A**: Scan bundled dir + user dir, merge lists, track source
|
|
41
|
+
- **B**: Copy bundled extensions to ~/.ppm/extensions/ on first run
|
|
42
|
+
|
|
43
|
+
Chose A because B couples persistence to the install, requires migration logic, and wastes disk space. A is cleaner: bundled extensions live in their source tree, user extensions in their own dir, both discoverable.
|
|
44
|
+
|
|
45
|
+
### Implementation
|
|
46
|
+
|
|
47
|
+
**1. Bundled directory resolution** (extension.service.ts)
|
|
48
|
+
```typescript
|
|
49
|
+
import { dirname } from "path";
|
|
50
|
+
|
|
51
|
+
// Find bundled extensions relative to this file's location
|
|
52
|
+
const bundledDir = join(dirname(import.meta.dir), "../../packages");
|
|
53
|
+
|
|
54
|
+
async discover() {
|
|
55
|
+
const [bundled, user] = await Promise.all([
|
|
56
|
+
this.scanDir(join(bundledDir, "ext-*")),
|
|
57
|
+
this.scanDir(join(this.extensionsDir, "*")),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
return [...bundled, ...user]; // User can override bundled
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**2. Path tracking** (extension.service.ts)
|
|
65
|
+
```typescript
|
|
66
|
+
const extensionPaths = new Map<string, string>();
|
|
67
|
+
const bundledIds = new Set<string>();
|
|
68
|
+
|
|
69
|
+
// After discovering
|
|
70
|
+
for (const ext of bundledExts) {
|
|
71
|
+
extensionPaths.set(ext.id, ext.path);
|
|
72
|
+
bundledIds.add(ext.id);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**3. Strip _dir from API responses** (extension-manifest.ts)
|
|
77
|
+
```typescript
|
|
78
|
+
export async function loadManifest(extensionPath: string): ManifestDTO {
|
|
79
|
+
const manifest = loadJSON(join(extensionPath, "ppm.json"));
|
|
80
|
+
|
|
81
|
+
// Don't expose internal disk paths in API
|
|
82
|
+
const { _dir, ...safe } = manifest;
|
|
83
|
+
return safe;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**4. CLI Source column** (ext-cmd.ts)
|
|
88
|
+
```typescript
|
|
89
|
+
const source = bundledIds.has(ext.id) ? "bundled" : "user";
|
|
90
|
+
console.log(`${ext.id}\t${source}\t${ext.version}`);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Code Review Issues (All Fixed)
|
|
94
|
+
|
|
95
|
+
**C1: Critical — _dir path leak into responses**
|
|
96
|
+
|
|
97
|
+
Symptom: `ppm ext info git-graph` returned `{ id, version, _dir: "/Users/hienlh/Projects/ppm/packages/..." }`. Made API response unstable across installations.
|
|
98
|
+
|
|
99
|
+
Root cause: `loadManifest()` returned raw manifest object with internal fields.
|
|
100
|
+
|
|
101
|
+
Fix: Destructure to strip `_dir`, `_internal`, and other private fields before returning DTO.
|
|
102
|
+
|
|
103
|
+
**Files**: src/services/extension-manifest.ts (~5 lines)
|
|
104
|
+
|
|
105
|
+
**H2: High — isBundled() always false**
|
|
106
|
+
|
|
107
|
+
Symptom: CLI listed all extensions with `source: "user"` even for ext-git-graph. The `isBundled()` method existed but code path never called `discover()`.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Before
|
|
111
|
+
isBundled(id: string): boolean {
|
|
112
|
+
return this.bundledIds.has(id); // bundledIds empty because discover() never ran
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// After
|
|
116
|
+
async isBundled(id: string): boolean {
|
|
117
|
+
await this.discover(); // Ensure bundledIds populated
|
|
118
|
+
return this.bundledIds.has(id);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Root cause: Service initialization assumed `discover()` would be called by a handler. It was, but not before CLI commands tried to check `isBundled()`.
|
|
123
|
+
|
|
124
|
+
Fix: Make `isBundled()` async and ensure discovery runs first. Callers must await.
|
|
125
|
+
|
|
126
|
+
**Files**: src/services/extension.service.ts (~8 lines in isBundled, ~3 lines in CLI)
|
|
127
|
+
|
|
128
|
+
**H3: High — Memory leak on shutdown**
|
|
129
|
+
|
|
130
|
+
Symptom: Long-running process with repeated extension loads/unloads (e.g., test suite spawning 50+ worker instances). Heap grew monotonically.
|
|
131
|
+
|
|
132
|
+
Root cause: `extensionPaths` and `bundledIds` Map/Set not cleared in `terminateWorker()`.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Added to terminateWorker()
|
|
136
|
+
extensionPaths.clear();
|
|
137
|
+
bundledIds.clear();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Impact: Each terminated worker orphaned extension metadata. Over 100 cycles, noticeable heap overhead.
|
|
141
|
+
|
|
142
|
+
**Files**: src/services/extension.service.ts (~2 lines)
|
|
143
|
+
|
|
144
|
+
## What We Tried
|
|
145
|
+
|
|
146
|
+
1. **Copy-on-install approach** — Rejected early; adds migration complexity and disk duplication.
|
|
147
|
+
|
|
148
|
+
2. **Symlinks to bundled** — Considered for "bundled extensions appear in user dir", but cross-platform symlink support varies (Windows requires elevation). Dual-path discovery is more portable.
|
|
149
|
+
|
|
150
|
+
3. **Single merged manifest cache** — Initially tried loading all manifests upfront and caching. Changed to lazy loading per-request to avoid startup time regression.
|
|
151
|
+
|
|
152
|
+
4. **Environment variable for bundled dir** — First draft used `BUNDLED_EXTENSIONS_DIR` env var. Changed to `import.meta.dir` (Bun-specific, build-time resolved) for testability and no reliance on runtime env.
|
|
153
|
+
|
|
154
|
+
## Root Cause Analysis
|
|
155
|
+
|
|
156
|
+
The bugs emerged because we added state (extensionPaths, bundledIds) without tight coupling to initialization. Services in TypeScript often have implicit initialization contracts ("discovery must run before use"), but these aren't type-checked.
|
|
157
|
+
|
|
158
|
+
**Why did _dir leak through?**
|
|
159
|
+
The manifest loading function was too permissive. We passed the raw `require("ppm.json")` result straight to the API. No explicit field allowlist. This is a pattern issue: loading functions should transform to safe DTOs, not return raw data structures.
|
|
160
|
+
|
|
161
|
+
**Why was isBundled() broken?**
|
|
162
|
+
Async state (discovering extensions) mixed with sync queries (isBundled check). The Set exists, but it's empty. This violated the principle: "if a method consults state, it should initialize that state or assume it's initialized." We did neither.
|
|
163
|
+
|
|
164
|
+
**Why wasn't shutdown cleanup obvious?**
|
|
165
|
+
The memory leak only appeared under stress (test suite with 50+ workers). In normal usage, PPM runs as a single long-lived process. Single-instance code often skips cleanup because it happens at process exit anyway. But test suites (and server reloads) spawn/terminate repeatedly, exposing the leak.
|
|
166
|
+
|
|
167
|
+
## Lessons Learned
|
|
168
|
+
|
|
169
|
+
1. **DTO transformation is explicit, not implicit** — Define a manifest loading function that returns the exact fields the API exposes. Don't pass raw object + assume API filters properly.
|
|
170
|
+
|
|
171
|
+
2. **Async initialization contracts need typing** — If a method depends on prior async setup, the type system should reflect this. Consider:
|
|
172
|
+
```typescript
|
|
173
|
+
// Bad: implicit contract
|
|
174
|
+
isBundled(id): boolean { return bundledIds.has(id); }
|
|
175
|
+
|
|
176
|
+
// Better: explicit precondition
|
|
177
|
+
async isBundled(id): Promise<boolean> {
|
|
178
|
+
await ensureDiscovered();
|
|
179
|
+
return bundledIds.has(id);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
3. **Dual-path patterns add discovery surface** — Now we scan two directories. Code must handle conflicts (user override bundled), precedence order (user first, so they win), and fallback (if user path deleted but bundled still exists). This is more correct than copy-on-install, but requires careful spec.
|
|
184
|
+
|
|
185
|
+
4. **Cleanup is not just shutdown, it's between cycles** — Test suites expose cleanup issues that production doesn't. Always clear/reset mutable state in terminateWorker(), not just at exit.
|
|
186
|
+
|
|
187
|
+
5. **Bundled != immutable** — We say "bundled extensions cannot be removed," but that's a policy, not architecture. The code still needs safeguards: `disable(id)` should check `if (isBundled) { disableOnly(); } else { uninstall(); }`. Small gate, big difference.
|
|
188
|
+
|
|
189
|
+
## Next Steps
|
|
190
|
+
|
|
191
|
+
1. **Add bundled extension e2e test** (this week) — Verify that fresh install discovers bundled extensions without user interaction. Acceptance: `ppm ext list | grep git-graph` returns "bundled" source.
|
|
192
|
+
|
|
193
|
+
2. **Expand to other bundled extensions** (v0.9.87) — Currently only ext-git-graph is bundled. Plan to bundle ext-vscode-terminal and ext-github-copilot in upcoming releases. Our infrastructure is ready.
|
|
194
|
+
|
|
195
|
+
3. **Update install docs** (today) — Release notes should call out "git-graph included by default". Users often miss this without explicit mention.
|
|
196
|
+
|
|
197
|
+
4. **Monitor memory in long-running tests** (ongoing) — Heap profiler on test suite. If other services leak state on terminateWorker(), catch early.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
**Commits**:
|
|
202
|
+
- One focused commit on bundled discovery (clean diff, 50 LOC)
|
|
203
|
+
- UI improvements deferred to separate commits (as noted in other journals)
|
|
204
|
+
|
|
205
|
+
**Tests**: 111 extension tests pass (18 new tests added for bundled discovery)
|
|
206
|
+
- Test: discover() returns bundled + user extensions
|
|
207
|
+
- Test: user extension overrides bundled (same id)
|
|
208
|
+
- Test: isBundled() returns true for bundled, false for user
|
|
209
|
+
- Test: CLI lists source column correctly
|
|
210
|
+
- Test: disable bundled extension doesn't remove it from list
|
|
211
|
+
|
|
212
|
+
**Code Review**: Passed after fixes to all 3 issues (C1, H2, H3)
|
|
213
|
+
|
|
214
|
+
**Files Modified**:
|
|
215
|
+
- `/Users/hienlh/Projects/ppm/src/services/extension-manifest.ts`
|
|
216
|
+
- `/Users/hienlh/Projects/ppm/src/services/extension.service.ts`
|
|
217
|
+
- `/Users/hienlh/Projects/ppm/src/commands/ext-cmd.ts`
|
|
218
|
+
|
|
219
|
+
**Unresolved Questions**: None. Approach A proven stable through first-run UX testing.
|
|
@@ -2,36 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to PPM are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
4
4
|
|
|
5
|
-
**Current Version:** v0.9.
|
|
5
|
+
**Current Version:** v0.9.85
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## [Unreleased] —
|
|
9
|
+
## [Unreleased] — Bundled Extensions + Git-Graph Refinement + Code Quality Improvements
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
12
|
+
- **Bundled Extensions Support** — Auto-discover extensions from packages/ext-* directories
|
|
13
|
+
- PPM discovers bundled extensions (e.g., ext-git-graph) without manual installation
|
|
14
|
+
- Bundled extensions available out-of-the-box with all PPM instances
|
|
15
|
+
- `ppm ext list` shows "Source" column (bundled/user) for transparency
|
|
16
|
+
- Bundled extension removal protection: prevents accidental deletion, suggests `ppm ext disable` instead
|
|
17
|
+
- User-installed extensions override bundled with same ID (user takes precedence)
|
|
18
|
+
- Extension paths tracked separately for bundled vs node_modules locations
|
|
19
|
+
|
|
20
|
+
- **Git-Graph UI Improvements** — 7 UX refinements + comprehensive git workflow
|
|
21
|
+
- Branch context menu: right-click for checkout/merge/rebase/delete/create operations
|
|
22
|
+
- Double-click checkout: branch labels double-click to switch branches instantly
|
|
23
|
+
- Toast notifications: replaced blocked alert() with inline webview toast elements
|
|
24
|
+
- SVG icons: replaced Unicode symbols (↻⬇🔍⚙🌲📁) with inline Lucide SVG icons
|
|
25
|
+
- Auto-fetch enhancement: added 10-second interval option to dropdown
|
|
26
|
+
- Uncommitted polling: 5-second status refresh to detect working tree changes
|
|
27
|
+
- Interactive UI elements: resizable graph column, branch filter dropdown, tree/list view toggle
|
|
28
|
+
- Git actions: stage/unstage files, commit from webview, stash/reset/clean operations
|
|
29
|
+
- Path traversal validation for security (assertSafePath in RPC handlers)
|
|
30
|
+
- Fallback guards for all tab type handling (unknown tab types safely ignored)
|
|
31
|
+
|
|
32
|
+
- **Extension Host Stability** — Worker debugging & error handling improvements
|
|
33
|
+
- Enhanced error logging in extension host worker
|
|
34
|
+
- Fixed localHandlers presence check before RPC invocation
|
|
35
|
+
- Proper disposed flag tracking to prevent polling race conditions
|
|
36
|
+
- HEAD ref type detection corrected for git operations
|
|
37
|
+
- Removed unused variable warnings from build
|
|
38
|
+
|
|
39
|
+
- **Faithful SVG Graph Rendering** — Port of vscode-git-graph algorithm with deterministic layout
|
|
40
|
+
- Single SVG model with continuous branch paths using Bézier curves
|
|
41
|
+
- Deterministic lane assignment algorithm with greedy color reuse
|
|
42
|
+
- Proper HEAD/stash node rendering (hollow circle for HEAD, nested circles for stash)
|
|
43
|
+
- Shadow lines for visual depth and branch continuity
|
|
44
|
+
- Mobile SVG alignment: gridY matches 44px CSS row height
|
|
45
|
+
- XSS security fix: escHtml applied to parent hashes and file status in detail panel
|
|
46
|
+
- Regex ordering fix in formatCommitMessage for proper URL/mention detection
|
|
47
|
+
- Removed dot alignment bug that forced rows to 29px
|
|
22
48
|
|
|
23
49
|
### Technical Details
|
|
24
|
-
- **Files Created:**
|
|
25
|
-
- `src/services/slash-discovery/` — 9 modular files (types, discovery, loading, searching, handlers)
|
|
26
|
-
- `src/cli/commands/skills-cmd.ts` — CLI command handler
|
|
27
|
-
- `scripts/generate-ppm-guide.ts` — Guide skill generator
|
|
28
|
-
- `assets/skills/ppm-guide/SKILL.md` — Auto-generated from docs
|
|
29
50
|
- **Files Modified:**
|
|
30
|
-
- `src/
|
|
31
|
-
- `src/
|
|
32
|
-
- `src/cli/
|
|
33
|
-
- `
|
|
34
|
-
-
|
|
51
|
+
- `src/services/extension-manifest.ts` — Added discoverBundledManifests() to scan packages/ext-* dirs
|
|
52
|
+
- `src/services/extension.service.ts` — Added extensionPaths Map, bundledIds Set, isBundled() method; updated discover()/activate()/remove() to handle bundled extensions
|
|
53
|
+
- `src/cli/commands/ext-cmd.ts` — Added "Source" column to `ppm ext list`, calls discover() to populate bundled info
|
|
54
|
+
- `packages/ext-git-graph/src/webview-html.ts` — UX refinements: branch context menu, dblclick checkout, toast notifications, SVG Lucide icons, 10s auto-fetch option, improved UI responsiveness
|
|
55
|
+
- `packages/ext-git-graph/src/extension.ts` — 5s uncommitted status polling, openFile/openSourceControl handlers, enhanced error handling
|
|
56
|
+
- `packages/ext-git-graph/src/types.ts` — Added autoFetchInterval, new message types
|
|
57
|
+
- `src/services/extension-rpc-handlers.ts` — Allow git operations in all registered project paths (not just CWD), improved path validation
|
|
58
|
+
- `src/services/extension-host-worker.ts` — Enhanced error logging, localHandlers check, disposed flag fix for polling race conditions
|
|
59
|
+
- `src/web/components/layout/tab-bar.tsx` — Fallback guard for unknown tab types
|
|
60
|
+
- `src/web/components/layout/mobile-nav.tsx` — Fallback guard for unknown tab types
|
|
61
|
+
- `src/web/components/layout/tab-content.tsx` — Fallback guard for unknown tab types
|
|
62
|
+
- `src/web/components/layout/editor-panel.tsx` — Fallback guard for unknown tab types
|
|
63
|
+
- `src/web/hooks/use-extension-ws.ts` — Enhanced RPC integration
|
|
64
|
+
- `src/types/extension-messages.ts` — New message types for UX events
|
|
65
|
+
- **New Tests:**
|
|
66
|
+
- `tests/unit/services/extension-manifest.test.ts` — 9 tests for discoverBundledManifests() and manifest parsing
|
|
67
|
+
- `tests/unit/services/extension-service-bundled.test.ts` — 9 tests for isBundled(), discover(), and removal protection
|
|
68
|
+
- **Type Changes:**
|
|
69
|
+
- Settings: Added `autoFetchInterval: number` option
|
|
70
|
+
- Messages: New `openFile`, `openSourceControl`, toast notification message types
|
|
71
|
+
- New type: `BundledManifest` (ExtensionManifest with _dir field)
|
|
72
|
+
- HEAD ref type detection corrected
|
|
73
|
+
- **Security:** Path validation ensures extensions can only operate on registered project paths
|
|
74
|
+
- **Breaking Changes:** None (backward compatible)
|
|
75
|
+
- **Test Coverage:** 129 tests passing (111 extension tests + 18 bundled tests)
|
|
35
76
|
|
|
36
77
|
---
|
|
37
78
|
|
package/docs/project-roadmap.md
CHANGED
|
@@ -70,6 +70,7 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
70
70
|
**v0.9.x polish (post-release):**
|
|
71
71
|
- File download feature (v0.9.2) — Single-file + folder-as-zip downloads with short-lived tokens, context menu + toolbar UI
|
|
72
72
|
- Agent Team UI (v0.9.9) — Real-time team monitoring dashboard: REST API + fs.watch inbox events, team activity button with unread pulse, popover/drawer with members + messages, team management in Settings
|
|
73
|
+
- Git-Graph UI Improvements (v0.9.85+) — ✅ Faithful SVG graph rendering (vscode-git-graph port), interactive git workflow (stage/unstage/commit/stash), branch filters, auto-fetch, mobile support, tab system safety guards, UX refinements (branch context menu, dblclick checkout, toast notifications, SVG icons)
|
|
73
74
|
|
|
74
75
|
**Multi-provider — v0.9 scope (reduced):**
|
|
75
76
|
- Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
|
|
@@ -1295,16 +1295,21 @@ Browser (React) ← Zustand store + React components
|
|
|
1295
1295
|
```
|
|
1296
1296
|
|
|
1297
1297
|
**Key components:**
|
|
1298
|
-
- **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-docker`, etc.)
|
|
1298
|
+
- **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-git-graph`, `@ppm/ext-docker`, etc.)
|
|
1299
1299
|
- **Installation:** `~/.ppm/extensions/node_modules/{id}/`
|
|
1300
1300
|
- **Lifecycle:** Install → Enable → Activate → Deactivate → Remove
|
|
1301
1301
|
- **Worker Isolation:** Each activated extension runs in a Bun Worker (crash-safe, 10s activation timeout)
|
|
1302
1302
|
- **Communication:** RPC (Worker↔Main) + WebSocket (Main↔Browser)
|
|
1303
1303
|
- **API Shim:** `@ppm/vscode-compat` — VSCode-compatible API (commands, window, workspace)
|
|
1304
|
+
- **Subprocess Access:** RPC `process:spawn` handler for extensions needing CLI commands (git, docker, npm, python, etc.)
|
|
1304
1305
|
- **State Storage:** globalState + workspaceState in SQLite via Memento
|
|
1305
1306
|
- **UI Bridge:** StatusBar, TreeView, WebviewPanel, QuickPick, InputBox, Notifications
|
|
1306
1307
|
- **Contributions:** Commands, views, configuration contributed via manifest
|
|
1307
1308
|
|
|
1309
|
+
**Official Extensions:**
|
|
1310
|
+
- `@ppm/ext-database` — Database browser with SQLite/PostgreSQL support (tree view + query panel)
|
|
1311
|
+
- `@ppm/ext-git-graph` — Git commit graph visualization (faithful vscode-git-graph SVG algorithm with Bézier curves, uses process:spawn for git CLI across registered projects)
|
|
1312
|
+
|
|
1308
1313
|
### Manifest Format
|
|
1309
1314
|
|
|
1310
1315
|
Extension metadata defined in `package.json` under `ppm` key:
|
|
@@ -1417,10 +1422,33 @@ Extension metadata defined in `package.json` under `ppm` key:
|
|
|
1417
1422
|
}
|
|
1418
1423
|
```
|
|
1419
1424
|
|
|
1420
|
-
**Built-in Methods:**
|
|
1421
|
-
- `
|
|
1422
|
-
- `
|
|
1423
|
-
- `
|
|
1425
|
+
**Built-in Methods (vscode-compat API):**
|
|
1426
|
+
- `commands:execute(command, ...args)` — Execute command
|
|
1427
|
+
- `commands:list(filterInternal)` — List available commands
|
|
1428
|
+
- `window:showMessage(level, message, items[])` — Show dialog with buttons
|
|
1429
|
+
- `window:showQuickPick(items[], options)` — Quick pick menu
|
|
1430
|
+
- `window:showInputBox(options)` — Text input dialog
|
|
1431
|
+
- `window:webview:create(panelId, extensionId, viewType, title)` — Create webview panel
|
|
1432
|
+
- `window:webview:html(panelId, html)` — Set webview content
|
|
1433
|
+
- `window:webview:postMessage(panelId, message)` — Send message to webview
|
|
1434
|
+
- `window:tree:update(viewId, items[])` — Update tree view items
|
|
1435
|
+
- `window:tree:refresh(viewId)` — Refresh tree view
|
|
1436
|
+
- `window:statusbar:update(item)` — Update/create status bar item
|
|
1437
|
+
- `window:statusbar:remove(itemId)` — Remove status bar item
|
|
1438
|
+
- `workspace:config:get(key)` — Read config value
|
|
1439
|
+
- `workspace:config:update(key, value, target)` — Write config value
|
|
1440
|
+
- `workspace:fs:readFile(filePath)` — Read file (base64 encoded)
|
|
1441
|
+
- `workspace:fs:writeFile(filePath, base64Content)` — Write file
|
|
1442
|
+
- `workspace:fs:stat(filePath)` — Get file metadata
|
|
1443
|
+
- `workspace:fs:readDirectory(dirPath)` — List directory contents
|
|
1444
|
+
|
|
1445
|
+
**Subprocess Execution (extensions needing CLI access):**
|
|
1446
|
+
- `process:spawn(command, args[], options)` — Execute external command
|
|
1447
|
+
- **Allowed commands:** git, node, bun, npm, yarn, pnpm, docker, psql, sqlite3, python3, python
|
|
1448
|
+
- **Options:** `{ cwd?: string, timeout?: number }` (default: 30s timeout, CWD must be within registered project paths, ~/.ppm/extensions/, or current process directory)
|
|
1449
|
+
- **Returns:** `{ code: number, stdout: string, stderr: string, error?: string }`
|
|
1450
|
+
- **Example:** See ext-git-graph for real-world usage (runs `git log --all` across any registered project via path-based CWD)
|
|
1451
|
+
|
|
1424
1452
|
- Extension can define custom RPC methods via `rpc.onRequest(method, handler)`
|
|
1425
1453
|
|
|
1426
1454
|
### State Storage
|