@hienlh/ppm 0.9.93 → 0.9.95
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/.opencode/.env.example +98 -0
- package/.opencode/skills/ads-management/scripts/.env.example +13 -0
- package/.opencode/skills/ai-multimodal/.env.example +230 -0
- package/.opencode/skills/cip-design/.env.example +6 -0
- package/.opencode/skills/devops/.env.example +76 -0
- package/.opencode/skills/docs-seeker/.env.example +15 -0
- package/.opencode/skills/elevenlabs/.env.example +3 -0
- package/.opencode/skills/marketing-dashboard/.env.example +15 -0
- package/.opencode/skills/marketing-dashboard/app/.env.example +2 -0
- package/.opencode/skills/marketing-dashboard/server/.env.example +2 -0
- package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
- package/.opencode/skills/mcp-management/scripts/dist/cli.js +160 -0
- package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
- package/.opencode/skills/payment-integration/scripts/.env.example +20 -0
- package/.opencode/skills/sequential-thinking/.env.example +8 -0
- package/CHANGELOG.md +15 -0
- package/bun.lock +6 -0
- package/dist/web/assets/ai-settings-section-LMO_cfIW.js +1 -0
- package/dist/web/assets/api-client-o_6TmLGC.js +1 -0
- package/dist/web/assets/api-settings-CoKe_BdR.js +1 -0
- package/dist/web/assets/architecture-PBZL5I3N-CUZIB1Vq.js +1 -0
- package/dist/web/assets/arrow-up-Dtrfv490.js +1 -0
- package/dist/web/assets/chat-tab-BRM81W0L.js +10 -0
- package/dist/web/assets/chevron-right-BzAdxJRG.js +1 -0
- package/dist/web/assets/code-CuravVys.js +1 -0
- package/dist/web/assets/code-editor-lxeFhLDX.js +8 -0
- package/dist/web/assets/columns-2-4fQcE4PF.js +1 -0
- package/dist/web/assets/conflict-editor-CO6NyEYJ.js +19 -0
- package/dist/web/assets/createLucideIcon-BjHrJDVb.js +1 -0
- package/dist/web/assets/{csv-preview-BZRICDP0.js → csv-preview-BizIVMyb.js} +2 -2
- package/dist/web/assets/database-D4DIhgi-.js +1 -0
- package/dist/web/assets/database-viewer-CU2X8VC-.js +2 -0
- package/dist/web/assets/diff-viewer-EybMrfw9.js +4 -0
- package/dist/web/assets/dist-C5IgeqrV.js +1 -0
- package/dist/web/assets/dist-im4ynINo.js +11 -0
- package/dist/web/assets/esm-K1XIK4vc.js +2 -0
- package/dist/web/assets/extension-store-3yZYn07W.js +1 -0
- package/dist/web/assets/extension-webview-DkacDy3f.js +3 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-CtOMUphQ.js +1 -0
- package/dist/web/assets/index-BZ4G-2BK.css +2 -0
- package/dist/web/assets/index-D7PJ14mf.js +26 -0
- package/dist/web/assets/info-3K5VOQVL-BCrPCWGY.js +1 -0
- package/dist/web/assets/input-CHRMley8.js +1 -0
- package/dist/web/assets/keybindings-store-BAuymsWd.js +1 -0
- package/dist/web/assets/keybindings-store-BKyNIeFB.js +1 -0
- package/dist/web/assets/{lib-DSLzfeW0.js → lib-D_kRA9p6.js} +1 -1
- package/dist/web/assets/markdown-renderer-ocvtw_4F.js +3 -0
- package/dist/web/assets/packet-RMMSAZCW-D_OqB-zi.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-WUHpLNJz.js +1 -0
- package/dist/web/assets/plus-51UQ45rf.js +1 -0
- package/dist/web/assets/port-forwarding-tab-Chz3t_rM.js +1 -0
- package/dist/web/assets/postgres-viewer-cf8Xbssy.js +3 -0
- package/dist/web/assets/project-store-Ciq-cK1O.js +1 -0
- package/dist/web/assets/radar-KQ55EAFF-HQIIecVM.js +1 -0
- package/dist/web/assets/react-GqWghJ-L.js +1 -0
- package/dist/web/assets/refresh-cw-CSFrDtiu.js +1 -0
- package/dist/web/assets/scroll-area-DwWF9FpN.js +1 -0
- package/dist/web/assets/settings-store-B470PCWf.js +2 -0
- package/dist/web/assets/settings-tab-1jARRAlz.js +1 -0
- package/dist/web/assets/{sql-query-editor-DaePHpQI.js → sql-query-editor-DZ9xskL8.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CveDk6KG.js +1 -0
- package/dist/web/assets/square-nsMa3iMk.js +1 -0
- package/dist/web/assets/tab-store-DZbiYk7y.js +1 -0
- package/dist/web/assets/table-Dq575bPF.js +1 -0
- package/dist/web/assets/terminal-tab-RvacNDWY.js +1 -0
- package/dist/web/assets/text-wrap-Cn6BNQfq.js +1 -0
- package/dist/web/assets/trash-2-CJYoLw7Q.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-0wLgUUTz.js +1 -0
- package/dist/web/assets/{use-monaco-theme-CM4IMROI.js → use-monaco-theme-OY18iXNi.js} +1 -1
- package/dist/web/assets/vendor-markdown-0Mxgxy0L.js +295 -0
- package/dist/web/assets/vendor-mermaid-B2SLgECS.js +2657 -0
- package/dist/web/assets/vendor-ui-B-T_damt.js +45 -0
- package/dist/web/assets/vendor-xterm-ejLe7-tK.js +36 -0
- package/dist/web/assets/x-DlFGzN8d.js +1 -0
- package/dist/web/index.html +26 -21
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +56 -4
- package/docs/journals/260415-frontend-memory-optimization.md +73 -0
- package/docs/project-changelog.md +11 -1
- package/docs/system-architecture.md +36 -0
- package/package.json +1 -1
- package/src/cli/commands/stop.ts +31 -0
- package/src/server/index.ts +68 -17
- package/src/services/autostart-generator.ts +1 -1
- package/src/services/autostart-register.ts +34 -29
- package/src/web/components/chat/message-list.tsx +59 -22
- package/src/web/components/chat/tool-cards.tsx +11 -4
- package/src/web/components/database/data-grid.tsx +2 -1
- package/src/web/components/editor/code-editor.tsx +14 -8
- package/src/web/components/editor/conflict-editor.tsx +2 -1
- package/src/web/components/editor/diff-viewer.tsx +2 -1
- package/src/web/components/editor/editor-breadcrumb.tsx +2 -1
- package/src/web/components/explorer/file-tree.tsx +6 -5
- package/src/web/components/explorer/search-panel.tsx +2 -1
- package/src/web/components/extensions/extension-webview.tsx +16 -30
- package/src/web/components/git/git-status-panel.tsx +2 -1
- package/src/web/components/layout/add-project-form.tsx +2 -1
- package/src/web/components/layout/mobile-drawer.tsx +2 -1
- package/src/web/components/layout/mobile-nav.tsx +2 -1
- package/src/web/components/layout/panel-layout.tsx +3 -3
- package/src/web/components/layout/project-bar.tsx +7 -6
- package/src/web/components/layout/project-bottom-sheet.tsx +2 -1
- package/src/web/components/layout/sidebar.tsx +5 -4
- package/src/web/components/layout/status-bar.tsx +5 -4
- package/src/web/components/layout/tab-bar.tsx +3 -3
- package/src/web/components/layout/tab-content.tsx +2 -1
- package/src/web/components/postgres/postgres-viewer.tsx +7 -5
- package/src/web/components/settings/settings-tab.tsx +2 -1
- package/src/web/components/shared/markdown-code-block.tsx +10 -8
- package/src/web/components/terminal/terminal-tab.tsx +3 -3
- package/src/web/hooks/use-chat.ts +4 -1
- package/vite.config.ts +17 -0
- package/dist/web/assets/_basePickBy-Bj0dI1ei.js +0 -1
- package/dist/web/assets/_baseUniq-CyzdZeQH.js +0 -1
- package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +0 -1
- package/dist/web/assets/api-client-BvxmRZUi.js +0 -1
- package/dist/web/assets/api-settings-CUxg9RE5.js +0 -1
- package/dist/web/assets/arc-CxgHJ7Z4.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +0 -1
- package/dist/web/assets/architectureDiagram-2XIMDMQ5-D16OotsC.js +0 -36
- package/dist/web/assets/array-BFDiaBgf.js +0 -1
- package/dist/web/assets/arrow-up-I9-21gkR.js +0 -1
- package/dist/web/assets/blockDiagram-WCTKOSBZ-Ct57Wtfk.js +0 -132
- package/dist/web/assets/c4Diagram-IC4MRINW-BIymcNsg.js +0 -10
- package/dist/web/assets/channel-wumTB1if.js +0 -1
- package/dist/web/assets/chat-tab-CC721_mQ.js +0 -10
- package/dist/web/assets/chevron-right-DY_wImxB.js +0 -1
- package/dist/web/assets/chunk-4BX2VUAB-CENmY7Kw.js +0 -1
- package/dist/web/assets/chunk-55IACEB6-DhZGI1l3.js +0 -1
- package/dist/web/assets/chunk-7E7YKBS2-DZcnC7Ow.js +0 -1
- package/dist/web/assets/chunk-7R4GIKGN-y8bfHEy-.js +0 -80
- package/dist/web/assets/chunk-C72U2L5F-BHPkfQj2.js +0 -1
- package/dist/web/assets/chunk-EGIJ26TM-nant2LXl.js +0 -1
- package/dist/web/assets/chunk-FMBD7UC4-Bog4cpN-.js +0 -15
- package/dist/web/assets/chunk-GEFDOKGD-86LFbsAC.js +0 -2
- package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +0 -1
- package/dist/web/assets/chunk-JSJVCQXG-23eG9mgt.js +0 -1
- package/dist/web/assets/chunk-KX2RTZJC-CHj8TnTB.js +0 -1
- package/dist/web/assets/chunk-KYZI473N-gqRLpJ4w.js +0 -53
- package/dist/web/assets/chunk-L3YUKLVL-DnSMmNFC.js +0 -1
- package/dist/web/assets/chunk-MX3YWQON-B6g1ZH9X.js +0 -1
- package/dist/web/assets/chunk-NQ4KR5QH-DX32345Y.js +0 -220
- package/dist/web/assets/chunk-O4XLMI2P-Vp_V4P-b.js +0 -7
- package/dist/web/assets/chunk-OZEHJAEY-lKq2SWjA.js +0 -1
- package/dist/web/assets/chunk-PQ6SQG4A-Bik13fTV.js +0 -1
- package/dist/web/assets/chunk-PU5JKC2W-DD95Rx35.js +0 -70
- package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +0 -1
- package/dist/web/assets/chunk-R5LLSJPH-dRhXRnrb.js +0 -1
- package/dist/web/assets/chunk-WL4C6EOR-B1iIvLOG.js +0 -189
- package/dist/web/assets/chunk-XIRO2GV7-DZBoNl1_.js +0 -1
- package/dist/web/assets/chunk-XPW4576I-CgLyyW03.js +0 -32
- package/dist/web/assets/chunk-XZSTWKYB-DjV8xl5A.js +0 -94
- package/dist/web/assets/chunk-YBOYWFTD-D_ILLe6_.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +0 -1
- package/dist/web/assets/clone--z5KLAuR.js +0 -1
- package/dist/web/assets/code-editor-BZ0xwZ4Z.js +0 -8
- package/dist/web/assets/columns-2-IeETSfON.js +0 -1
- package/dist/web/assets/conflict-editor-Bwls2-yk.js +0 -19
- package/dist/web/assets/cose-bilkent-S5V4N54A-BGNPFv3x.js +0 -1
- package/dist/web/assets/cytoscape.esm-C8i2jUzT.js +0 -321
- package/dist/web/assets/dagre-CkhlMHnx.js +0 -1
- package/dist/web/assets/dagre-KLK3FWXG-Cnp996VG.js +0 -4
- package/dist/web/assets/database-CgTomMxt.js +0 -1
- package/dist/web/assets/database-viewer-DiXWqOJH.js +0 -2
- package/dist/web/assets/defaultLocale-ZeknFqNe.js +0 -1
- package/dist/web/assets/diagram-E7M64L7V-BZF0tSOr.js +0 -24
- package/dist/web/assets/diagram-IFDJBPK2-nUcO8sN8.js +0 -43
- package/dist/web/assets/diagram-P4PSJMXO-CW0eCkwC.js +0 -24
- package/dist/web/assets/diff-viewer-CC-RmeV5.js +0 -4
- package/dist/web/assets/dist-CM0oD8tQ.js +0 -1
- package/dist/web/assets/dist-DZmJeHOA.js +0 -1
- package/dist/web/assets/erDiagram-INFDFZHY-DSkriYZ9.js +0 -70
- package/dist/web/assets/extension-webview-C1d6fezE.js +0 -3
- package/dist/web/assets/flowDiagram-PKNHOUZH-CFYAfZBx.js +0 -162
- package/dist/web/assets/ganttDiagram-A5KZAMGK-KSn4XAU4.js +0 -292
- package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +0 -1
- package/dist/web/assets/gitGraphDiagram-K3NZZRJ6-BMgjjVys.js +0 -65
- package/dist/web/assets/graphlib-BWe1iK_s.js +0 -1
- package/dist/web/assets/index-BcIyrJiY.js +0 -26
- package/dist/web/assets/index-Chf0otez.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +0 -2
- package/dist/web/assets/init-0VJVrkRJ.js +0 -1
- package/dist/web/assets/input-BHj0veau.js +0 -45
- package/dist/web/assets/isArrayLikeObject-ClzWCpcm.js +0 -1
- package/dist/web/assets/isEmpty-BfLnxq-B.js +0 -1
- package/dist/web/assets/ishikawaDiagram-PHBUUO56-CiVEvp8o.js +0 -70
- package/dist/web/assets/journeyDiagram-4ABVD52K-CG_v5Aho.js +0 -139
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
- package/dist/web/assets/kanban-definition-K7BYSVSG-miB0-_Zq.js +0 -89
- package/dist/web/assets/keybindings-store-BIufrOzJ.js +0 -1
- package/dist/web/assets/line-CSuSrJ9J.js +0 -1
- package/dist/web/assets/linear-DFN_MPsw.js +0 -1
- package/dist/web/assets/markdown-renderer-C5UPA1-7.js +0 -306
- package/dist/web/assets/math-CRc16Nj6.js +0 -1
- package/dist/web/assets/mermaid-parser.core-CFdP1Z5_.js +0 -4
- package/dist/web/assets/mindmap-definition-YRQLILUH-pYPWwASE.js +0 -68
- package/dist/web/assets/ordinal-DpFn432U.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +0 -1
- package/dist/web/assets/path-INs8XTPH.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +0 -1
- package/dist/web/assets/pieDiagram-SKSYHLDU-Dovdlvhu.js +0 -30
- package/dist/web/assets/plus-DQGIb4mQ.js +0 -1
- package/dist/web/assets/port-forwarding-tab-DmifthYH.js +0 -1
- package/dist/web/assets/postgres-viewer-Bo7jEQfQ.js +0 -13
- package/dist/web/assets/preload-helper-mr3rCizq.js +0 -1
- package/dist/web/assets/quadrantDiagram-337W2JSQ-TXe6cU_F.js +0 -7
- package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +0 -1
- package/dist/web/assets/react-0tkk-ztn.js +0 -1
- package/dist/web/assets/react-dom-Bpkvzu3U.js +0 -1
- package/dist/web/assets/react-nm2Ru1Pt.js +0 -1
- package/dist/web/assets/refresh-cw-Clk8fdUD.js +0 -1
- package/dist/web/assets/requirementDiagram-Z7DCOOCP-CuiiuGS9.js +0 -73
- package/dist/web/assets/rough.esm-eLccZ4OJ.js +0 -1
- package/dist/web/assets/sankeyDiagram-WA2Y5GQK-BbRmhv0t.js +0 -10
- package/dist/web/assets/scroll-area-BpXCNme3.js +0 -1
- package/dist/web/assets/sequenceDiagram-2WXFIKYE-B2D8IQDb.js +0 -145
- package/dist/web/assets/settings-tab-D9GicyA9.js +0 -1
- package/dist/web/assets/sqlite-viewer-pacZlViY.js +0 -1
- package/dist/web/assets/square-vBdqj0bF.js +0 -1
- package/dist/web/assets/src-CqyWLlNZ.js +0 -1
- package/dist/web/assets/stateDiagram-RAJIS63D-ylr4HxPu.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +0 -1
- package/dist/web/assets/table-Bi27fEaN.js +0 -1
- package/dist/web/assets/terminal-tab-DpzE3yoD.js +0 -36
- package/dist/web/assets/text-wrap-D_OmSzhp.js +0 -1
- package/dist/web/assets/timeline-definition-YZTLITO2-pMv1grvM.js +0 -61
- package/dist/web/assets/trash-2-CNuB-htI.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +0 -1
- package/dist/web/assets/vennDiagram-LZ73GAT5-C-rkIUbo.js +0 -34
- package/dist/web/assets/x-Dw3TjeY_.js +0 -1
- package/dist/web/assets/xychartDiagram-JWTSCODW-CtpjAakO.js +0 -7
- /package/dist/web/assets/{csv-parser-i7fjqP2H.js → csv-parser--2WJNgS7.js} +0 -0
- /package/dist/web/assets/{katex-DR0kdMDv.js → katex-CKoArbIw.js} +0 -0
- /package/dist/web/assets/{chunk-CFjPhJqf.js → rolldown-runtime-FhOqtrmT.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-B8uUWWej.js → sql-completion-provider-C3cq9j99.js} +0 -0
- /package/dist/web/assets/{utils-DX8jb5qv.js → utils-ChWX7pZv.js} +0 -0
- /package/dist/web/assets/{terminal-tab-BrP-ENHg.css → vendor-xterm-BrP-ENHg.css} +0 -0
|
@@ -60,34 +60,37 @@ function removeMetadata(): void {
|
|
|
60
60
|
|
|
61
61
|
// ─── macOS ──────────────────────────────────────────────────────────────
|
|
62
62
|
|
|
63
|
-
async function enableMacOS(config: AutoStartConfig): Promise<string> {
|
|
63
|
+
async function enableMacOS(config: AutoStartConfig, opts?: { skipStart?: boolean }): Promise<string> {
|
|
64
64
|
const plistPath = getPlistPath();
|
|
65
65
|
const plistDir = dirname(plistPath);
|
|
66
66
|
|
|
67
67
|
if (!existsSync(plistDir)) mkdirSync(plistDir, { recursive: true });
|
|
68
68
|
writeFileSync(plistPath, generatePlist(config));
|
|
69
69
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const result = Bun.spawnSync({
|
|
78
|
-
cmd: ["launchctl", "bootstrap", `gui/${process.getuid!()}`, plistPath],
|
|
79
|
-
stdout: "pipe", stderr: "pipe",
|
|
80
|
-
});
|
|
70
|
+
// Skip loading if supervisor is already running from direct spawn
|
|
71
|
+
if (!opts?.skipStart) {
|
|
72
|
+
// Unload first if already loaded (ignore errors)
|
|
73
|
+
Bun.spawnSync({
|
|
74
|
+
cmd: ["launchctl", "bootout", `gui/${process.getuid!()}`, plistPath],
|
|
75
|
+
stdout: "ignore", stderr: "ignore",
|
|
76
|
+
});
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
cmd: ["launchctl", "load", plistPath],
|
|
78
|
+
// Load the agent
|
|
79
|
+
const result = Bun.spawnSync({
|
|
80
|
+
cmd: ["launchctl", "bootstrap", `gui/${process.getuid!()}`, plistPath],
|
|
86
81
|
stdout: "pipe", stderr: "pipe",
|
|
87
82
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
|
|
84
|
+
if (result.exitCode !== 0) {
|
|
85
|
+
// Fallback to legacy syntax
|
|
86
|
+
const legacy = Bun.spawnSync({
|
|
87
|
+
cmd: ["launchctl", "load", plistPath],
|
|
88
|
+
stdout: "pipe", stderr: "pipe",
|
|
89
|
+
});
|
|
90
|
+
if (legacy.exitCode !== 0) {
|
|
91
|
+
const err = legacy.stderr.toString().trim();
|
|
92
|
+
throw new Error(`launchctl load failed: ${err}`);
|
|
93
|
+
}
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -146,7 +149,7 @@ function statusMacOS(): AutoStartStatus {
|
|
|
146
149
|
|
|
147
150
|
// ─── Linux ──────────────────────────────────────────────────────────────
|
|
148
151
|
|
|
149
|
-
async function enableLinux(config: AutoStartConfig): Promise<string> {
|
|
152
|
+
async function enableLinux(config: AutoStartConfig, opts?: { skipStart?: boolean }): Promise<string> {
|
|
150
153
|
const servicePath = getServicePath();
|
|
151
154
|
const serviceDir = dirname(servicePath);
|
|
152
155
|
|
|
@@ -171,11 +174,13 @@ async function enableLinux(config: AutoStartConfig): Promise<string> {
|
|
|
171
174
|
throw new Error(`systemctl enable failed: ${enable.stderr.toString().trim()}`);
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
// Start
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
// Start (skip if supervisor is already running from direct spawn)
|
|
178
|
+
if (!opts?.skipStart) {
|
|
179
|
+
Bun.spawnSync({
|
|
180
|
+
cmd: ["systemctl", "--user", "start", "ppm.service"],
|
|
181
|
+
stdout: "ignore", stderr: "ignore",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
179
184
|
|
|
180
185
|
// Enable lingering so service runs at boot without login
|
|
181
186
|
Bun.spawnSync({
|
|
@@ -312,11 +317,11 @@ function statusWindows(): AutoStartStatus {
|
|
|
312
317
|
|
|
313
318
|
// ─── Public API ─────────────────────────────────────────────────────────
|
|
314
319
|
|
|
315
|
-
/** Enable auto-start for the current platform */
|
|
316
|
-
export async function enableAutoStart(config: AutoStartConfig): Promise<string> {
|
|
320
|
+
/** Enable auto-start for the current platform. skipStart=true registers without starting (when supervisor is already running). */
|
|
321
|
+
export async function enableAutoStart(config: AutoStartConfig, opts?: { skipStart?: boolean }): Promise<string> {
|
|
317
322
|
const platform = process.platform;
|
|
318
|
-
if (platform === "darwin") return enableMacOS(config);
|
|
319
|
-
if (platform === "linux") return enableLinux(config);
|
|
323
|
+
if (platform === "darwin") return enableMacOS(config, opts);
|
|
324
|
+
if (platform === "linux") return enableLinux(config, opts);
|
|
320
325
|
if (platform === "win32") return enableWindows(config);
|
|
321
326
|
throw new Error(`Auto-start not supported on ${platform}`);
|
|
322
327
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, useMemo, useCallback } from "react";
|
|
1
|
+
import { useEffect, useRef, useState, useMemo, useCallback, memo, lazy, Suspense } from "react";
|
|
2
2
|
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
|
3
3
|
import { getAuthToken } from "@/lib/api-client";
|
|
4
4
|
import type { ChatMessage, ChatEvent } from "../../../types/chat";
|
|
5
5
|
import type { SessionPhase } from "../../../types/api";
|
|
6
6
|
import { ToolCard } from "./tool-cards";
|
|
7
|
-
|
|
7
|
+
const MarkdownRenderer = lazy(() =>
|
|
8
|
+
import("@/components/shared/markdown-renderer").then((m) => ({ default: m.MarkdownRenderer }))
|
|
9
|
+
);
|
|
8
10
|
import { cn, basename } from "@/lib/utils";
|
|
9
11
|
|
|
10
12
|
import {
|
|
@@ -67,6 +69,34 @@ export function MessageList({
|
|
|
67
69
|
}: MessageListProps) {
|
|
68
70
|
// Scroll handled by StickToBottom wrapper — no manual scroll logic needed
|
|
69
71
|
|
|
72
|
+
const PAGE_SIZE = 50;
|
|
73
|
+
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
|
74
|
+
|
|
75
|
+
// Reset visible count when conversation identity changes (not on every streaming tick)
|
|
76
|
+
const conversationId = messages[0]?.id;
|
|
77
|
+
useEffect(() => { setVisibleCount(PAGE_SIZE); }, [conversationId]);
|
|
78
|
+
|
|
79
|
+
const filtered = useMemo(() => messages.filter((msg) => {
|
|
80
|
+
const hasContent = msg.content && msg.content.trim().length > 0;
|
|
81
|
+
const hasEvents = msg.events && msg.events.length > 0;
|
|
82
|
+
// User bubbles only render text — hide SDK tool-result user messages
|
|
83
|
+
// that have no text content (their events are merged into assistant)
|
|
84
|
+
if (msg.role === "user") return hasContent;
|
|
85
|
+
return hasContent || hasEvents;
|
|
86
|
+
}), [messages]);
|
|
87
|
+
|
|
88
|
+
const displayed = useMemo(() => {
|
|
89
|
+
const start = Math.max(0, filtered.length - visibleCount);
|
|
90
|
+
return filtered.slice(start);
|
|
91
|
+
}, [filtered, visibleCount]);
|
|
92
|
+
|
|
93
|
+
const hasMore = visibleCount < filtered.length;
|
|
94
|
+
|
|
95
|
+
// Stable fork handler — avoids new closure per message (preserves MessageBubble memo)
|
|
96
|
+
const handleFork = useCallback((msgContent: string, msgId: string | undefined) => {
|
|
97
|
+
onFork?.(msgContent, msgId);
|
|
98
|
+
}, [onFork]);
|
|
99
|
+
|
|
70
100
|
if (messagesLoading) {
|
|
71
101
|
return (
|
|
72
102
|
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
|
|
@@ -85,32 +115,30 @@ export function MessageList({
|
|
|
85
115
|
);
|
|
86
116
|
}
|
|
87
117
|
|
|
88
|
-
const filtered = useMemo(() => messages.filter((msg) => {
|
|
89
|
-
const hasContent = msg.content && msg.content.trim().length > 0;
|
|
90
|
-
const hasEvents = msg.events && msg.events.length > 0;
|
|
91
|
-
// User bubbles only render text — hide SDK tool-result user messages
|
|
92
|
-
// that have no text content (their events are merged into assistant)
|
|
93
|
-
if (msg.role === "user") return hasContent;
|
|
94
|
-
return hasContent || hasEvents;
|
|
95
|
-
}), [messages]);
|
|
96
|
-
|
|
97
118
|
return (
|
|
98
119
|
<div className="relative flex-1 overflow-hidden flex flex-col min-h-0">
|
|
99
120
|
<StickToBottom className="flex-1 overflow-y-auto overflow-x-hidden" resize="smooth" initial="instant">
|
|
100
121
|
<StickToBottom.Content className="p-4 space-y-4">
|
|
101
|
-
{
|
|
122
|
+
{hasMore && (
|
|
123
|
+
<button onClick={() => setVisibleCount((c) => c + PAGE_SIZE)}
|
|
124
|
+
className="w-full py-2 text-xs text-text-secondary hover:text-text-primary bg-surface-elevated/50 hover:bg-surface-elevated rounded-md border border-border/50 transition-colors">
|
|
125
|
+
Load {Math.min(PAGE_SIZE, filtered.length - visibleCount)} more messages...
|
|
126
|
+
</button>
|
|
127
|
+
)}
|
|
128
|
+
{displayed.map((msg, idx) => {
|
|
129
|
+
const globalIdx = filtered.length - displayed.length + idx;
|
|
130
|
+
const prevMsg = globalIdx > 0 ? filtered[globalIdx - 1] : undefined;
|
|
131
|
+
return (
|
|
102
132
|
<MessageBubble
|
|
103
133
|
key={msg.id}
|
|
104
134
|
message={msg}
|
|
105
135
|
isStreaming={isStreaming && msg.id.startsWith("streaming-")}
|
|
106
136
|
projectName={projectName}
|
|
107
|
-
onFork={msg.role === "user" && onFork ?
|
|
108
|
-
|
|
109
|
-
const prevMsg = idx > 0 ? filtered[idx - 1] : undefined;
|
|
110
|
-
onFork(msg.content, prevMsg?.sdkUuid ?? prevMsg?.id);
|
|
111
|
-
} : undefined}
|
|
137
|
+
onFork={msg.role === "user" && onFork ? handleFork : undefined}
|
|
138
|
+
prevMsgId={prevMsg?.sdkUuid ?? prevMsg?.id}
|
|
112
139
|
/>
|
|
113
|
-
)
|
|
140
|
+
);
|
|
141
|
+
})}
|
|
114
142
|
|
|
115
143
|
{pendingApproval && (
|
|
116
144
|
pendingApproval.tool === "AskUserQuestion"
|
|
@@ -142,10 +170,15 @@ function ScrollToBottomButton() {
|
|
|
142
170
|
);
|
|
143
171
|
}
|
|
144
172
|
|
|
145
|
-
function MessageBubble({ message, isStreaming, projectName, onFork }: {
|
|
173
|
+
const MessageBubble = memo(function MessageBubble({ message, isStreaming, projectName, onFork, prevMsgId }: {
|
|
174
|
+
message: ChatMessage; isStreaming: boolean; projectName?: string;
|
|
175
|
+
onFork?: (content: string, messageId: string | undefined) => void;
|
|
176
|
+
prevMsgId?: string
|
|
177
|
+
}) {
|
|
146
178
|
if (message.role === "user") {
|
|
179
|
+
const handleFork = onFork ? () => onFork(message.content, prevMsgId) : undefined;
|
|
147
180
|
return (
|
|
148
|
-
<UserBubble content={message.content} projectName={projectName} onFork={
|
|
181
|
+
<UserBubble content={message.content} projectName={projectName} onFork={handleFork} />
|
|
149
182
|
);
|
|
150
183
|
}
|
|
151
184
|
|
|
@@ -175,7 +208,7 @@ function MessageBubble({ message, isStreaming, projectName, onFork }: { message:
|
|
|
175
208
|
)}
|
|
176
209
|
</div>
|
|
177
210
|
);
|
|
178
|
-
}
|
|
211
|
+
});
|
|
179
212
|
|
|
180
213
|
/** Image extensions that can be previewed inline */
|
|
181
214
|
const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
@@ -843,7 +876,11 @@ function stripTeammateMessages(text: string): string {
|
|
|
843
876
|
function MarkdownContent({ content, projectName, isStreaming }: { content: string; projectName?: string; isStreaming?: boolean }) {
|
|
844
877
|
const cleaned = stripTeammateMessages(content);
|
|
845
878
|
if (!cleaned) return null;
|
|
846
|
-
return
|
|
879
|
+
return (
|
|
880
|
+
<Suspense fallback={<div className="animate-pulse h-4 bg-muted rounded" />}>
|
|
881
|
+
<MarkdownRenderer content={cleaned} projectName={projectName} codeActions isStreaming={isStreaming} />
|
|
882
|
+
</Suspense>
|
|
883
|
+
);
|
|
847
884
|
}
|
|
848
885
|
|
|
849
886
|
/* ToolCard, ToolSummary, ToolDetails extracted to ./tool-cards.tsx */
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Tool card components for chat message rendering.
|
|
3
3
|
* Handles summary + details for all SDK tool types.
|
|
4
4
|
*/
|
|
5
|
-
import { useState, useMemo } from "react";
|
|
6
|
-
|
|
5
|
+
import { useState, useMemo, lazy, Suspense } from "react";
|
|
6
|
+
const MarkdownRenderer = lazy(() =>
|
|
7
|
+
import("@/components/shared/markdown-renderer").then((m) => ({ default: m.MarkdownRenderer }))
|
|
8
|
+
);
|
|
7
9
|
import {
|
|
8
10
|
ChevronDown,
|
|
9
11
|
ChevronRight,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
Columns2,
|
|
21
23
|
} from "lucide-react";
|
|
22
24
|
import type { ChatEvent } from "../../../types/chat";
|
|
25
|
+
import { useShallow } from "zustand/react/shallow";
|
|
23
26
|
import { useTabStore } from "@/stores/tab-store";
|
|
24
27
|
import { basename } from "@/lib/utils";
|
|
25
28
|
|
|
@@ -159,7 +162,7 @@ function ToolDetails({
|
|
|
159
162
|
projectName?: string;
|
|
160
163
|
}) {
|
|
161
164
|
const s = (v: unknown) => String(v ?? "");
|
|
162
|
-
const { openTab } = useTabStore();
|
|
165
|
+
const { openTab } = useTabStore(useShallow((state) => ({ openTab: state.openTab })));
|
|
163
166
|
|
|
164
167
|
/** Open a file in a new editor tab */
|
|
165
168
|
const openFile = (filePath: string) => {
|
|
@@ -451,7 +454,11 @@ function SubagentChildren({ events, projectName }: { events: ChatEvent[]; projec
|
|
|
451
454
|
|
|
452
455
|
/** Inline markdown renderer for tool details (prompt, result) */
|
|
453
456
|
function MiniMarkdown({ content, maxHeight = "max-h-48" }: { content: string; maxHeight?: string }) {
|
|
454
|
-
return
|
|
457
|
+
return (
|
|
458
|
+
<Suspense fallback={<div className="animate-pulse h-4 bg-muted rounded" />}>
|
|
459
|
+
<MarkdownRenderer content={content} className={`text-text-secondary overflow-auto ${maxHeight}`} />
|
|
460
|
+
</Suspense>
|
|
461
|
+
);
|
|
455
462
|
}
|
|
456
463
|
|
|
457
464
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, useRef, memo, useEffect } from "react";
|
|
2
2
|
import { Loader2, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, Trash2, Plus, Search, X, Eye, Filter, Pin, PinOff, Columns3 } from "lucide-react";
|
|
3
|
+
import { useShallow } from "zustand/react/shallow";
|
|
3
4
|
import { useTabStore } from "@/stores/tab-store";
|
|
4
5
|
import type { DbColumnInfo } from "./use-database";
|
|
5
6
|
import { ExportButton } from "./export-button";
|
|
@@ -42,7 +43,7 @@ export function DataGrid({
|
|
|
42
43
|
const [insertValues, setInsertValues] = useState<Record<string, string>>({});
|
|
43
44
|
const [insertError, setInsertError] = useState<string | null>(null);
|
|
44
45
|
const [confirmBulkDelete, setConfirmBulkDelete] = useState(false);
|
|
45
|
-
const { openTab } = useTabStore();
|
|
46
|
+
const { openTab } = useTabStore(useShallow((s) => ({ openTab: s.openTab })));
|
|
46
47
|
const openCellViewer = useCallback((cell: { col: string; value: string }) => {
|
|
47
48
|
openTab({
|
|
48
49
|
type: "editor",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useEffect, useState, useCallback, useRef, useMemo } from "react";
|
|
1
|
+
import { useEffect, useState, useCallback, useRef, useMemo, memo, lazy, Suspense } from "react";
|
|
2
2
|
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
3
3
|
import type * as MonacoType from "monaco-editor";
|
|
4
|
-
import { MarkdownRenderer } from "@/components/shared/markdown-renderer";
|
|
5
4
|
import { api, projectUrl, getAuthToken } from "@/lib/api-client";
|
|
5
|
+
import { useShallow } from "zustand/react/shallow";
|
|
6
6
|
import { useTabStore } from "@/stores/tab-store";
|
|
7
7
|
import { usePanelStore } from "@/stores/panel-store";
|
|
8
8
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
@@ -12,10 +12,12 @@ import { Loader2, FileWarning, ExternalLink, Play, Database } from "lucide-react
|
|
|
12
12
|
import { EditorBreadcrumb } from "./editor-breadcrumb";
|
|
13
13
|
import { EditorToolbar } from "./editor-toolbar";
|
|
14
14
|
import { SaveAsDialog } from "./save-as-dialog";
|
|
15
|
-
import { lazy, Suspense } from "react";
|
|
16
15
|
import { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from "../database/sql-completion-provider";
|
|
17
16
|
import { useConnections, type Connection } from "../database/use-connections";
|
|
18
17
|
|
|
18
|
+
const MarkdownRenderer = lazy(() =>
|
|
19
|
+
import("@/components/shared/markdown-renderer").then((m) => ({ default: m.MarkdownRenderer }))
|
|
20
|
+
);
|
|
19
21
|
const CsvPreview = lazy(() => import("./csv-preview").then((m) => ({ default: m.CsvPreview })));
|
|
20
22
|
|
|
21
23
|
/** Image extensions renderable inline */
|
|
@@ -47,7 +49,7 @@ interface CodeEditorProps {
|
|
|
47
49
|
tabId?: string;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
52
|
+
export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
51
53
|
const filePath = metadata?.filePath as string | undefined;
|
|
52
54
|
const projectName = metadata?.projectName as string | undefined;
|
|
53
55
|
// Inline content mode: read-only Monaco with pre-loaded content (e.g. cell viewer)
|
|
@@ -61,8 +63,8 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
61
63
|
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
62
64
|
const latestContentRef = useRef<string>("");
|
|
63
65
|
const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);
|
|
64
|
-
const { tabs, updateTab } = useTabStore();
|
|
65
|
-
const { wordWrap, toggleWordWrap } = useSettingsStore();
|
|
66
|
+
const { tabs, updateTab } = useTabStore(useShallow((s) => ({ tabs: s.tabs, updateTab: s.updateTab })));
|
|
67
|
+
const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));
|
|
66
68
|
const monacoTheme = useMonacoTheme();
|
|
67
69
|
|
|
68
70
|
const isUntitled = metadata?.isUntitled === true;
|
|
@@ -553,10 +555,14 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
553
555
|
)}
|
|
554
556
|
</div>
|
|
555
557
|
);
|
|
556
|
-
}
|
|
558
|
+
});
|
|
557
559
|
|
|
558
560
|
function MarkdownPreview({ content }: { content: string }) {
|
|
559
|
-
return
|
|
561
|
+
return (
|
|
562
|
+
<Suspense fallback={<div className="animate-pulse h-4 bg-muted rounded m-4" />}>
|
|
563
|
+
<MarkdownRenderer content={content} className="flex-1 overflow-auto p-4" />
|
|
564
|
+
</Suspense>
|
|
565
|
+
);
|
|
560
566
|
}
|
|
561
567
|
|
|
562
568
|
function ImagePreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
@@ -2,6 +2,7 @@ import { useEffect, useState, useRef, useCallback } from "react";
|
|
|
2
2
|
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
3
3
|
import type * as MonacoType from "monaco-editor";
|
|
4
4
|
import { api, projectUrl } from "@/lib/api-client";
|
|
5
|
+
import { useShallow } from "zustand/react/shallow";
|
|
5
6
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
6
7
|
import { useMonacoTheme } from "@/lib/use-monaco-theme";
|
|
7
8
|
import { Loader2 } from "lucide-react";
|
|
@@ -100,7 +101,7 @@ export function ConflictEditor({ metadata }: ConflictEditorProps) {
|
|
|
100
101
|
const widgetsRef = useRef<MonacoType.editor.IContentWidget[]>([]);
|
|
101
102
|
const decorationsRef = useRef<MonacoType.editor.IEditorDecorationsCollection | null>(null);
|
|
102
103
|
|
|
103
|
-
const { wordWrap } = useSettingsStore();
|
|
104
|
+
const { wordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap })));
|
|
104
105
|
const monacoTheme = useMonacoTheme();
|
|
105
106
|
|
|
106
107
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState, useMemo, useRef } from "react";
|
|
2
2
|
import { DiffEditor } from "@monaco-editor/react";
|
|
3
3
|
import { api, projectUrl } from "@/lib/api-client";
|
|
4
|
+
import { useShallow } from "zustand/react/shallow";
|
|
4
5
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
5
6
|
import { useMonacoTheme } from "@/lib/use-monaco-theme";
|
|
6
7
|
import { Loader2, FileCode, PanelLeftOpen, PanelRightOpen, Columns2, WrapText } from "lucide-react";
|
|
@@ -40,7 +41,7 @@ export function DiffViewer({ metadata }: DiffViewerProps) {
|
|
|
40
41
|
const [loading, setLoading] = useState(!isInline);
|
|
41
42
|
const [error, setError] = useState<string | null>(null);
|
|
42
43
|
const [expandMode, setExpandMode] = useState<"both" | "left" | "right">("both");
|
|
43
|
-
const { wordWrap, toggleWordWrap } = useSettingsStore();
|
|
44
|
+
const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));
|
|
44
45
|
const monacoTheme = useMonacoTheme();
|
|
45
46
|
|
|
46
47
|
// Measure container height — Monaco needs explicit pixel height on mobile
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
DropdownMenuSubContent,
|
|
11
11
|
} from "@/components/ui/dropdown-menu";
|
|
12
12
|
import { useFileStore, type FileNode } from "@/stores/file-store";
|
|
13
|
+
import { useShallow } from "zustand/react/shallow";
|
|
13
14
|
import { useTabStore } from "@/stores/tab-store";
|
|
14
15
|
import { basename } from "@/lib/utils";
|
|
15
16
|
|
|
@@ -83,7 +84,7 @@ interface EditorBreadcrumbProps {
|
|
|
83
84
|
|
|
84
85
|
export function EditorBreadcrumb({ filePath, projectName, tabId, className }: EditorBreadcrumbProps) {
|
|
85
86
|
const tree = useFileStore((s) => s.tree);
|
|
86
|
-
const { updateTab, openTab } = useTabStore();
|
|
87
|
+
const { updateTab, openTab } = useTabStore(useShallow((s) => ({ updateTab: s.updateTab, openTab: s.openTab })));
|
|
87
88
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
88
89
|
|
|
89
90
|
const segments = useMemo(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useCallback, useState } from "react";
|
|
1
|
+
import { useEffect, useCallback, useState, memo } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Folder,
|
|
4
4
|
FolderOpen,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Download,
|
|
13
13
|
Loader2,
|
|
14
14
|
} from "lucide-react";
|
|
15
|
+
import { useShallow } from "zustand/react/shallow";
|
|
15
16
|
import { useFileStore, type FileNode } from "@/stores/file-store";
|
|
16
17
|
import { useProjectStore } from "@/stores/project-store";
|
|
17
18
|
import { useTabStore } from "@/stores/tab-store";
|
|
@@ -58,8 +59,8 @@ interface TreeNodeProps {
|
|
|
58
59
|
onFileOpen?: () => void;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function TreeNode({ node, depth, projectName, onAction, onFileOpen }: TreeNodeProps) {
|
|
62
|
-
const { expandedPaths, toggleExpand, selectedFiles, toggleFileSelect } = useFileStore();
|
|
62
|
+
const TreeNode = memo(function TreeNode({ node, depth, projectName, onAction, onFileOpen }: TreeNodeProps) {
|
|
63
|
+
const { expandedPaths, toggleExpand, selectedFiles, toggleFileSelect } = useFileStore(useShallow((s) => ({ expandedPaths: s.expandedPaths, toggleExpand: s.toggleExpand, selectedFiles: s.selectedFiles, toggleFileSelect: s.toggleFileSelect })));
|
|
63
64
|
const openTab = useTabStore((s) => s.openTab);
|
|
64
65
|
const isExpanded = expandedPaths.has(node.path);
|
|
65
66
|
const isDir = node.type === "directory";
|
|
@@ -187,14 +188,14 @@ function TreeNode({ node, depth, projectName, onAction, onFileOpen }: TreeNodePr
|
|
|
187
188
|
))}
|
|
188
189
|
</div>
|
|
189
190
|
);
|
|
190
|
-
}
|
|
191
|
+
});
|
|
191
192
|
|
|
192
193
|
interface FileTreeProps {
|
|
193
194
|
onFileOpen?: () => void;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
197
|
-
const { tree, loading, error, fetchTree, reset, selectedFiles, clearSelection } = useFileStore();
|
|
198
|
+
const { tree, loading, error, fetchTree, reset, selectedFiles, clearSelection } = useFileStore(useShallow((s) => ({ tree: s.tree, loading: s.loading, error: s.error, fetchTree: s.fetchTree, reset: s.reset, selectedFiles: s.selectedFiles, clearSelection: s.clearSelection })));
|
|
198
199
|
const activeProject = useProjectStore((s) => s.activeProject);
|
|
199
200
|
const openTab = useTabStore((s) => s.openTab);
|
|
200
201
|
const [actionState, setActionState] = useState<{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
2
|
import { Search, CaseSensitive, ChevronRight, ChevronDown, FileText, X, Loader2, WholeWord, Regex, ReplaceAll } from "lucide-react";
|
|
3
|
+
import { useShallow } from "zustand/react/shallow";
|
|
3
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
4
5
|
import { useTabStore } from "@/stores/tab-store";
|
|
5
6
|
import { projectUrl, api } from "@/lib/api-client";
|
|
@@ -62,7 +63,7 @@ function OptionButton({ active, onClick, title, children }: { active: boolean; o
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
export function SearchPanel() {
|
|
65
|
-
const { activeProject } = useProjectStore();
|
|
66
|
+
const { activeProject } = useProjectStore(useShallow((s) => ({ activeProject: s.activeProject })));
|
|
66
67
|
const openTab = useTabStore((s) => s.openTab);
|
|
67
68
|
|
|
68
69
|
const [query, setQuery] = useState("");
|
|
@@ -54,47 +54,33 @@ export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
|
54
54
|
const rawHtml = panel?.html ?? "";
|
|
55
55
|
const html = injectVscodeApiShim(rawHtml);
|
|
56
56
|
|
|
57
|
-
// On reload: resolve project path
|
|
58
|
-
//
|
|
57
|
+
// On reload: resolve project path and dispatch command once
|
|
58
|
+
// No retry — if it fails, user closes tab and reopens to retry
|
|
59
59
|
useEffect(() => {
|
|
60
60
|
if (panel || !viewType) return;
|
|
61
|
-
// Mark project as "dispatched" so project-sync effect doesn't double-dispatch
|
|
62
61
|
if (projectName) prevProjectRef.current = projectName;
|
|
63
62
|
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
64
63
|
let cancelled = false;
|
|
65
|
-
let resolvedArgs: unknown[] | null = null;
|
|
66
64
|
|
|
67
|
-
async function
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
resolvedArgs = [];
|
|
65
|
+
async function dispatch() {
|
|
66
|
+
let args: unknown[] = [];
|
|
67
|
+
if (projectName) {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch("/api/projects");
|
|
70
|
+
const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };
|
|
71
|
+
const match = json.data?.find((p) => p.name === projectName);
|
|
72
|
+
if (match) args = [match.path];
|
|
73
|
+
} catch {}
|
|
77
74
|
}
|
|
78
|
-
return resolvedArgs;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function attempt() {
|
|
82
|
-
const args = await resolveArgs();
|
|
83
75
|
if (cancelled) return;
|
|
84
76
|
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
85
77
|
detail: { command, args },
|
|
86
78
|
}));
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
}, 500);
|
|
93
|
-
const retryTimer = setInterval(() => {
|
|
94
|
-
if (!cancelled) attempt();
|
|
95
|
-
}, 2_000);
|
|
96
|
-
|
|
97
|
-
return () => { cancelled = true; clearTimeout(initialTimer); clearInterval(retryTimer); };
|
|
81
|
+
// Short delay to let WS connect after page load
|
|
82
|
+
const timer = setTimeout(dispatch, 500);
|
|
83
|
+
return () => { cancelled = true; clearTimeout(timer); };
|
|
98
84
|
}, [panel, viewType, projectName]);
|
|
99
85
|
|
|
100
86
|
// When panel exists, ensure correct project is loaded.
|
|
@@ -168,10 +154,10 @@ export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
|
168
154
|
};
|
|
169
155
|
}, []);
|
|
170
156
|
|
|
171
|
-
// Timeout: if panel doesn't appear within
|
|
157
|
+
// Timeout: if panel doesn't appear within 5s, show error
|
|
172
158
|
useEffect(() => {
|
|
173
159
|
if (panel) { setTimedOut(false); return; }
|
|
174
|
-
const timer = setTimeout(() => setTimedOut(true),
|
|
160
|
+
const timer = setTimeout(() => setTimedOut(true), 5_000);
|
|
175
161
|
return () => clearTimeout(timer);
|
|
176
162
|
}, [panel]);
|
|
177
163
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "lucide-react";
|
|
16
16
|
import { api, projectUrl } from "@/lib/api-client";
|
|
17
17
|
import { basename } from "@/lib/utils";
|
|
18
|
+
import { useShallow } from "zustand/react/shallow";
|
|
18
19
|
import { useTabStore } from "@/stores/tab-store";
|
|
19
20
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
20
21
|
import { useProjectStore } from "@/stores/project-store";
|
|
@@ -117,7 +118,7 @@ export function GitStatusPanel({ metadata, tabId, onNavigate }: GitStatusPanelPr
|
|
|
117
118
|
label: string;
|
|
118
119
|
files: string[];
|
|
119
120
|
} | null>(null);
|
|
120
|
-
const { openTab } = useTabStore();
|
|
121
|
+
const { openTab } = useTabStore(useShallow((s) => ({ openTab: s.openTab })));
|
|
121
122
|
const viewMode = useSettingsStore((s) => s.gitStatusViewMode);
|
|
122
123
|
const setViewMode = useSettingsStore((s) => s.setGitStatusViewMode);
|
|
123
124
|
const activeProjectPath = useProjectStore((s) =>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from "react";
|
|
2
2
|
import { Loader2, FolderOpen } from "lucide-react";
|
|
3
|
+
import { useShallow } from "zustand/react/shallow";
|
|
3
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
4
5
|
import { api } from "@/lib/api-client";
|
|
5
6
|
import { cn } from "@/lib/utils";
|
|
@@ -18,7 +19,7 @@ interface AddProjectFormProps {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function AddProjectForm({ onSuccess, onCancel, footerClassName }: AddProjectFormProps) {
|
|
21
|
-
const { addProject } = useProjectStore();
|
|
22
|
+
const { addProject } = useProjectStore(useShallow((s) => ({ addProject: s.addProject })));
|
|
22
23
|
const [path, setPath] = useState("");
|
|
23
24
|
const [name, setName] = useState("");
|
|
24
25
|
const [suggestions, setSuggestions] = useState<SuggestedDir[]>([]);
|
|
@@ -2,6 +2,7 @@ import { useState, useCallback, useEffect } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
X, Bug, FolderOpen, GitBranch, Settings, Database,
|
|
4
4
|
} from "lucide-react";
|
|
5
|
+
import { useShallow } from "zustand/react/shallow";
|
|
5
6
|
import { useProjectStore } from "@/stores/project-store";
|
|
6
7
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
7
8
|
import { FileTree } from "@/components/explorer/file-tree";
|
|
@@ -28,7 +29,7 @@ interface MobileDrawerProps {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function MobileDrawer({ isOpen, onClose, initialTab }: MobileDrawerProps) {
|
|
31
|
-
const { activeProject } = useProjectStore();
|
|
32
|
+
const { activeProject } = useProjectStore(useShallow((s) => ({ activeProject: s.activeProject })));
|
|
32
33
|
const version = useSettingsStore((s) => s.version);
|
|
33
34
|
const [activeTab, setActiveTab] = useState<DrawerTab>(initialTab ?? "explorer");
|
|
34
35
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ChevronRight, Globe, Puzzle, Copy, Download, Pencil, Trash2,
|
|
6
6
|
} from "lucide-react";
|
|
7
7
|
import { usePanelStore } from "@/stores/panel-store";
|
|
8
|
+
import { useShallow } from "zustand/react/shallow";
|
|
8
9
|
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
9
10
|
import { useFileStore, type FileNode } from "@/stores/file-store";
|
|
10
11
|
import { findPanelPosition, MAX_ROWS } from "@/stores/panel-utils";
|
|
@@ -152,7 +153,7 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
// Active project avatar for the Projects button
|
|
155
|
-
const { activeProject, projects, customOrder } = useProjectStore();
|
|
156
|
+
const { activeProject, projects, customOrder } = useProjectStore(useShallow((s) => ({ activeProject: s.activeProject, projects: s.projects, customOrder: s.customOrder })));
|
|
156
157
|
const ordered = resolveOrder(projects, customOrder ?? null);
|
|
157
158
|
const allNames = ordered.map((p) => p.name);
|
|
158
159
|
const activeIdx = ordered.findIndex((p) => p.name === activeProject?.name);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, memo } from "react";
|
|
2
2
|
import { Panel, Group, Separator } from "react-resizable-panels";
|
|
3
3
|
import { GripVertical, GripHorizontal } from "lucide-react";
|
|
4
4
|
import { usePanelStore } from "@/stores/panel-store";
|
|
@@ -10,7 +10,7 @@ interface PanelLayoutProps {
|
|
|
10
10
|
projectName: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function PanelLayout({ projectName }: PanelLayoutProps) {
|
|
13
|
+
export const PanelLayout = memo(function PanelLayout({ projectName }: PanelLayoutProps) {
|
|
14
14
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
15
15
|
const grid = usePanelStore((s) =>
|
|
16
16
|
s.currentProject === projectName ? s.grid : (s.projectGrids[projectName] ?? [[]]),
|
|
@@ -51,7 +51,7 @@ export function PanelLayout({ projectName }: PanelLayoutProps) {
|
|
|
51
51
|
))}
|
|
52
52
|
</Group>
|
|
53
53
|
);
|
|
54
|
-
}
|
|
54
|
+
});
|
|
55
55
|
|
|
56
56
|
function RowGroup({ row, rowIdx, totalRows, projectName }: { row: string[]; rowIdx: number; totalRows: number; projectName: string }) {
|
|
57
57
|
const defaultSize = `${Math.round(100 / totalRows)}%`;
|