@hienlh/ppm 0.9.92 → 0.9.94
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 +15 -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-DQNdrUvL.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-B4XNYHnl.js +8 -0
- package/dist/web/assets/columns-2-4fQcE4PF.js +1 -0
- package/dist/web/assets/conflict-editor-BcsRDSCw.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-CfzAAtm3.js +2 -0
- package/dist/web/assets/diff-viewer-DgA9z9Ux.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-gHGB2Nw2.js +3 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-CtOMUphQ.js +1 -0
- package/dist/web/assets/index-B4mGNywE.js +26 -0
- package/dist/web/assets/index-BZ4G-2BK.css +2 -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-sIjU5LtB.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-BMXnuRuI.js +1 -0
- package/dist/web/assets/postgres-viewer-B6Wj5xiN.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-BKQo79HU.js +1 -0
- package/dist/web/assets/{sql-query-editor-DkBHKM2i.js → sql-query-editor-DZ9xskL8.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CytNesG3.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-DjfxKMSB.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-BEX-OBkP.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/packages/ext-git-graph/src/extension.ts +5 -20
- 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 +9 -3
- 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/src/web/hooks/use-extension-ws.ts +29 -10
- 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-B3sYWC6s.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-GQ-BPapW.js +0 -8
- package/dist/web/assets/columns-2-IeETSfON.js +0 -1
- package/dist/web/assets/conflict-editor-BzEbBMdo.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-hoe7AAOc.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-tjZrVKtL.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-BeqBWL7v.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-Chf0otez.css +0 -2
- package/dist/web/assets/index-fzSsl_Dg.js +0 -26
- 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-DpSn1fWY.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-Pch6Q5AB.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-2emRdRba.js +0 -1
- package/dist/web/assets/postgres-viewer-3yrH60xU.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-DdyI-_Vr.js +0 -1
- package/dist/web/assets/sqlite-viewer-DnCkYz-y.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-C5lCh18X.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
|
@@ -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("");
|
|
@@ -30,7 +30,8 @@ export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
|
30
30
|
const panelId = metadata?.panelId as string | undefined;
|
|
31
31
|
const viewType = metadata?.viewType as string | undefined;
|
|
32
32
|
const currentProject = useTabStore((s) => s.currentProject);
|
|
33
|
-
|
|
33
|
+
// Prefer currentProject (reflects URL/active project) over stale tab metadata
|
|
34
|
+
const projectName = currentProject || (metadata?.projectName as string | undefined) || undefined;
|
|
34
35
|
const [timedOut, setTimedOut] = useState(false);
|
|
35
36
|
|
|
36
37
|
// Match panel: prefer panelId (exact), fallback to viewType match (reload recovery)
|
|
@@ -57,6 +58,8 @@ export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
|
57
58
|
// Retry needed because WS connection may not be ready on first attempt
|
|
58
59
|
useEffect(() => {
|
|
59
60
|
if (panel || !viewType) return;
|
|
61
|
+
// Mark project as "dispatched" so project-sync effect doesn't double-dispatch
|
|
62
|
+
if (projectName) prevProjectRef.current = projectName;
|
|
60
63
|
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
61
64
|
let cancelled = false;
|
|
62
65
|
let resolvedArgs: unknown[] | null = null;
|
|
@@ -199,8 +202,10 @@ export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
|
199
202
|
return () => window.removeEventListener("ext:webview:message", handler);
|
|
200
203
|
}, [resolvedPanelId]);
|
|
201
204
|
|
|
202
|
-
// Loading state — waiting for extension to create the panel
|
|
203
|
-
|
|
205
|
+
// Loading state — waiting for extension to create the panel AND deliver HTML.
|
|
206
|
+
// We must wait for HTML before mounting the iframe because browsers don't
|
|
207
|
+
// re-execute scripts when React updates the srcDoc attribute from "" to content.
|
|
208
|
+
if (!panel || !rawHtml) {
|
|
204
209
|
return (
|
|
205
210
|
<div className="flex flex-col items-center justify-center h-full gap-3 text-sm text-text-subtle">
|
|
206
211
|
{timedOut ? (
|
|
@@ -230,6 +235,7 @@ export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
|
230
235
|
<div className="h-full w-full relative">
|
|
231
236
|
<iframe
|
|
232
237
|
ref={iframeRef}
|
|
238
|
+
key={resolvedPanelId}
|
|
233
239
|
srcDoc={html}
|
|
234
240
|
sandbox="allow-scripts"
|
|
235
241
|
className="w-full h-full border-0 bg-white dark:bg-zinc-900"
|
|
@@ -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)}%`;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo, useRef, useEffect } from "react";
|
|
1
|
+
import { useState, useCallback, useMemo, useRef, useEffect, memo } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { Plus, Settings, Pencil, Trash2, Palette, Bug, Cloud, X, Copy } from "lucide-react";
|
|
4
4
|
import { CloudSharePopover } from "./cloud-share-popover";
|
|
5
5
|
import { openBugReportPopup } from "@/lib/report-bug";
|
|
6
|
+
import { useShallow } from "zustand/react/shallow";
|
|
6
7
|
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
7
8
|
import { useTabStore } from "@/stores/tab-store";
|
|
8
9
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
@@ -29,7 +30,7 @@ import { cn } from "@/lib/utils";
|
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
// Avatar circle
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
|
-
function ProjectAvatar({ name, color, active, allNames }: {
|
|
33
|
+
const ProjectAvatar = memo(function ProjectAvatar({ name, color, active, allNames }: {
|
|
33
34
|
name: string; color: string; active: boolean; allNames: string[];
|
|
34
35
|
}) {
|
|
35
36
|
const initials = getProjectInitials(name, allNames);
|
|
@@ -51,7 +52,7 @@ function ProjectAvatar({ name, color, active, allNames }: {
|
|
|
51
52
|
)}
|
|
52
53
|
</div>
|
|
53
54
|
);
|
|
54
|
-
}
|
|
55
|
+
});
|
|
55
56
|
|
|
56
57
|
// ---------------------------------------------------------------------------
|
|
57
58
|
// Color picker popover (inline in dialog)
|
|
@@ -78,8 +79,8 @@ function ColorPicker({ current, onChange }: { current: string; onChange: (c: str
|
|
|
78
79
|
// ---------------------------------------------------------------------------
|
|
79
80
|
// ProjectBar
|
|
80
81
|
// ---------------------------------------------------------------------------
|
|
81
|
-
export function ProjectBar() {
|
|
82
|
-
const { projects, activeProject, setActiveProject, setProjectColor, reorderProjects, renameProject, deleteProject, customOrder } = useProjectStore();
|
|
82
|
+
export const ProjectBar = memo(function ProjectBar() {
|
|
83
|
+
const { projects, activeProject, setActiveProject, setProjectColor, reorderProjects, renameProject, deleteProject, customOrder } = useProjectStore(useShallow((s) => ({ projects: s.projects, activeProject: s.activeProject, setActiveProject: s.setActiveProject, setProjectColor: s.setProjectColor, reorderProjects: s.reorderProjects, renameProject: s.renameProject, deleteProject: s.deleteProject, customOrder: s.customOrder })));
|
|
83
84
|
const openTab = useTabStore((s) => s.openTab);
|
|
84
85
|
const version = useSettingsStore((s) => s.version);
|
|
85
86
|
const handleReportBug = useCallback(() => openBugReportPopup(version), [version]);
|
|
@@ -435,4 +436,4 @@ export function ProjectBar() {
|
|
|
435
436
|
</aside>
|
|
436
437
|
</div>
|
|
437
438
|
);
|
|
438
|
-
}
|
|
439
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useRef, useCallback } from "react";
|
|
2
2
|
import { X, Check, Plus, Settings, ChevronUp, ChevronDown, Pencil, Trash2, Palette, ArrowLeft } from "lucide-react";
|
|
3
|
+
import { useShallow } from "zustand/react/shallow";
|
|
3
4
|
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
4
5
|
import { useTabStore } from "@/stores/tab-store";
|
|
5
6
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
@@ -34,7 +35,7 @@ function ProjectAvatar({ name, color, allNames }: { name: string; color: string;
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function ProjectBottomSheet({ isOpen, onClose }: ProjectBottomSheetProps) {
|
|
37
|
-
const { projects, activeProject, setActiveProject, setProjectColor, reorderProjects, renameProject, deleteProject, customOrder } = useProjectStore();
|
|
38
|
+
const { projects, activeProject, setActiveProject, setProjectColor, reorderProjects, renameProject, deleteProject, customOrder } = useProjectStore(useShallow((s) => ({ projects: s.projects, activeProject: s.activeProject, setActiveProject: s.setActiveProject, setProjectColor: s.setProjectColor, reorderProjects: s.reorderProjects, renameProject: s.renameProject, deleteProject: s.deleteProject, customOrder: s.customOrder })));
|
|
38
39
|
|
|
39
40
|
const openTab = useTabStore((s) => s.openTab);
|
|
40
41
|
const version = useSettingsStore((s) => s.version);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useCallback, useRef, useMemo } from "react";
|
|
1
|
+
import { useCallback, useRef, useMemo, memo } from "react";
|
|
2
2
|
import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database, Search, Puzzle } from "lucide-react";
|
|
3
|
+
import { useShallow } from "zustand/react/shallow";
|
|
3
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
4
5
|
import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
|
|
5
6
|
import { useExtensionStore } from "@/stores/extension-store";
|
|
@@ -58,8 +59,8 @@ function ResizeHandle({ onResize }: { onResize: (width: number) => void }) {
|
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
export function Sidebar() {
|
|
62
|
-
const { activeProject } = useProjectStore();
|
|
62
|
+
export const Sidebar = memo(function Sidebar() {
|
|
63
|
+
const { activeProject } = useProjectStore(useShallow((s) => ({ activeProject: s.activeProject })));
|
|
63
64
|
const sidebarCollapsed = useSettingsStore((s) => s.sidebarCollapsed);
|
|
64
65
|
const sidebarWidth = useSettingsStore((s) => s.sidebarWidth);
|
|
65
66
|
const toggleSidebar = useSettingsStore((s) => s.toggleSidebar);
|
|
@@ -169,4 +170,4 @@ export function Sidebar() {
|
|
|
169
170
|
<ResizeHandle onResize={setSidebarWidth} />
|
|
170
171
|
</aside>
|
|
171
172
|
);
|
|
172
|
-
}
|
|
173
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
1
2
|
import { useExtensionStore, type StatusBarItemUI } from "@/stores/extension-store";
|
|
2
3
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
3
4
|
|
|
4
5
|
/** Fixed status bar at the bottom of the editor area (hidden on mobile) */
|
|
5
|
-
export function StatusBar() {
|
|
6
|
+
export const StatusBar = memo(function StatusBar() {
|
|
6
7
|
const items = useExtensionStore((s) => s.statusBarItems);
|
|
7
8
|
|
|
8
9
|
const left = items
|
|
@@ -27,9 +28,9 @@ export function StatusBar() {
|
|
|
27
28
|
</div>
|
|
28
29
|
</div>
|
|
29
30
|
);
|
|
30
|
-
}
|
|
31
|
+
});
|
|
31
32
|
|
|
32
|
-
function StatusBarEntry({ item }: { item: StatusBarItemUI }) {
|
|
33
|
+
const StatusBarEntry = memo(function StatusBarEntry({ item }: { item: StatusBarItemUI }) {
|
|
33
34
|
const content = (
|
|
34
35
|
<button
|
|
35
36
|
className={`truncate px-1 rounded-sm transition-colors ${
|
|
@@ -61,4 +62,4 @@ function StatusBarEntry({ item }: { item: StatusBarItemUI }) {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
return content;
|
|
64
|
-
}
|
|
65
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useCallback, useState } from "react";
|
|
1
|
+
import { useEffect, useRef, useCallback, useState, memo } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Plus,
|
|
4
4
|
Terminal,
|
|
@@ -47,7 +47,7 @@ interface TabBarProps {
|
|
|
47
47
|
panelId?: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export function TabBar({ panelId }: TabBarProps) {
|
|
50
|
+
export const TabBar = memo(function TabBar({ panelId }: TabBarProps) {
|
|
51
51
|
const activeProject = useProjectStore((s) => s.activeProject);
|
|
52
52
|
const tabRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
|
|
53
53
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
@@ -267,5 +267,5 @@ export function TabBar({ panelId }: TabBarProps) {
|
|
|
267
267
|
)}
|
|
268
268
|
</>
|
|
269
269
|
);
|
|
270
|
-
}
|
|
270
|
+
});
|
|
271
271
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Suspense, lazy } from "react";
|
|
2
|
+
import { useShallow } from "zustand/react/shallow";
|
|
2
3
|
import { useTabStore, type TabType } from "@/stores/tab-store";
|
|
3
4
|
import { Loader2 } from "lucide-react";
|
|
4
5
|
|
|
@@ -74,7 +75,7 @@ function LoadingFallback() {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
export function TabContent() {
|
|
77
|
-
const { tabs, activeTabId } = useTabStore();
|
|
78
|
+
const { tabs, activeTabId } = useTabStore(useShallow((s) => ({ tabs: s.tabs, activeTabId: s.activeTabId })));
|
|
78
79
|
|
|
79
80
|
if (tabs.length === 0) {
|
|
80
81
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
1
|
+
import { useState, useCallback, useMemo, useEffect, useRef, lazy, Suspense } from "react";
|
|
2
2
|
import { Database, Loader2, AlertCircle, Play, ChevronLeft, ChevronRight, Table, RefreshCw } from "lucide-react";
|
|
3
3
|
import { useReactTable, getCoreRowModel, flexRender, type ColumnDef } from "@tanstack/react-table";
|
|
4
|
-
|
|
4
|
+
const CodeMirror = lazy(() => import("@uiw/react-codemirror"));
|
|
5
5
|
import { sql, PostgreSQL } from "@codemirror/lang-sql";
|
|
6
6
|
import { usePostgres, type PgColumnInfo, type PgQueryResult } from "./use-postgres";
|
|
7
7
|
|
|
@@ -244,9 +244,11 @@ function PgQueryEditor({ onExecute, result, error, loading }: {
|
|
|
244
244
|
<div className="flex flex-col h-full overflow-hidden">
|
|
245
245
|
<div className="flex items-start gap-1 border-b border-border bg-background" onKeyDown={handleKeyDown}>
|
|
246
246
|
<div className="flex-1 max-h-[120px] overflow-auto">
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
<Suspense fallback={<div className="p-2 text-xs text-text-secondary">Loading editor...</div>}>
|
|
248
|
+
<CodeMirror value={query} onChange={setQuery} extensions={[sql({ dialect: PostgreSQL })]}
|
|
249
|
+
basicSetup={{ lineNumbers: false, foldGutter: false, highlightActiveLine: false }}
|
|
250
|
+
className="text-xs [&_.cm-editor]:!outline-none [&_.cm-scroller]:!overflow-auto" />
|
|
251
|
+
</Suspense>
|
|
250
252
|
</div>
|
|
251
253
|
<button type="button" onClick={handleExecute} disabled={loading} title="Execute (Cmd+Enter)"
|
|
252
254
|
className="shrink-0 m-1 p-1.5 rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors">
|
|
@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
|
|
|
7
7
|
import { Input } from "@/components/ui/input";
|
|
8
8
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
9
9
|
import { Separator } from "@/components/ui/separator";
|
|
10
|
+
import { useShallow } from "zustand/react/shallow";
|
|
10
11
|
import { useSettingsStore, type Theme } from "@/stores/settings-store";
|
|
11
12
|
import { cn } from "@/lib/utils";
|
|
12
13
|
import { AISettingsSection } from "./ai-settings-section";
|
|
@@ -41,7 +42,7 @@ const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; ic
|
|
|
41
42
|
];
|
|
42
43
|
|
|
43
44
|
export function SettingsTab() {
|
|
44
|
-
const { theme, setTheme, deviceName, setDeviceName, version } = useSettingsStore();
|
|
45
|
+
const { theme, setTheme, deviceName, setDeviceName, version } = useSettingsStore(useShallow((s) => ({ theme: s.theme, setTheme: s.setTheme, deviceName: s.deviceName, setDeviceName: s.setDeviceName, version: s.version })));
|
|
45
46
|
const { permission, isSubscribed, loading, error: pushError, subscribe, unsubscribe } = usePushNotification();
|
|
46
47
|
const [activeCategory, setActiveCategory] = useState<SettingsCategory | null>(null);
|
|
47
48
|
const [nameInput, setNameInput] = useState(deviceName ?? "");
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, type ReactNode } from "react";
|
|
2
|
-
import mermaid from "mermaid";
|
|
3
2
|
import { useMdContext, FILE_EXT_RE, GLOB_CHARS_RE } from "./markdown-context";
|
|
4
3
|
import { useTabStore } from "@/stores/tab-store";
|
|
5
4
|
|
|
6
5
|
const MERMAID_KEYWORDS = /^(sequenceDiagram|flowchart|graph\s|classDiagram|stateDiagram|erDiagram|journey|gantt|pie|quadrantChart|requirementDiagram|gitGraph|mindmap|timeline|sankey|xychart|block-beta|packet-beta|architecture-beta|kanban)\b/;
|
|
7
6
|
|
|
8
|
-
let
|
|
9
|
-
function
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
let mermaidPromise: Promise<typeof import("mermaid")["default"]> | null = null;
|
|
8
|
+
function getMermaid() {
|
|
9
|
+
if (!mermaidPromise) {
|
|
10
|
+
mermaidPromise = import("mermaid").then((mod) => {
|
|
11
|
+
mod.default.initialize({ startOnLoad: false, theme: "default", securityLevel: "loose", fontFamily: "ui-sans-serif, system-ui, sans-serif" });
|
|
12
|
+
return mod.default;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return mermaidPromise;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/** Extract plain text from a hast node tree */
|
|
@@ -89,9 +92,8 @@ function MermaidDiagram({ source }: { source: string }) {
|
|
|
89
92
|
const [svg, setSvg] = useState<string | null>(null);
|
|
90
93
|
|
|
91
94
|
useEffect(() => {
|
|
92
|
-
ensureMermaidInit();
|
|
93
95
|
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
|
|
94
|
-
|
|
96
|
+
getMermaid().then((m) => m.render(id, source)).then(({ svg }) => setSvg(svg)).catch(() => {});
|
|
95
97
|
}, [source]);
|
|
96
98
|
|
|
97
99
|
if (!svg) return <pre><code>{source}</code></pre>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef, useEffect, useState, useCallback } from "react";
|
|
1
|
+
import { useRef, useEffect, useState, useCallback, memo } from "react";
|
|
2
2
|
import { useTerminal } from "@/hooks/use-terminal";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { Copy, ClipboardPaste } from "lucide-react";
|
|
@@ -18,7 +18,7 @@ const MOBILE_KEYS = [
|
|
|
18
18
|
{ label: "\u2192", value: "\x1b[C" },
|
|
19
19
|
] as const;
|
|
20
20
|
|
|
21
|
-
export function TerminalTab({ metadata }: TerminalTabProps) {
|
|
21
|
+
export const TerminalTab = memo(function TerminalTab({ metadata }: TerminalTabProps) {
|
|
22
22
|
const sessionId = (metadata?.sessionId as string) ?? "new";
|
|
23
23
|
const projectName = metadata?.projectName as string | undefined;
|
|
24
24
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -153,4 +153,4 @@ export function TerminalTab({ metadata }: TerminalTabProps) {
|
|
|
153
153
|
)}
|
|
154
154
|
</div>
|
|
155
155
|
);
|
|
156
|
-
}
|
|
156
|
+
});
|