@akiojin/gwt 4.1.1 → 4.3.0
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/README.md +28 -3
- package/dist/claude.d.ts +4 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +13 -1
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +68 -68
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +3 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +13 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +6 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/client/assets/index-ChHC-Puh.css +1 -0
- package/dist/client/assets/index-PqK9jkug.js +78 -0
- package/dist/client/index.html +2 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +3 -0
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +10 -1
- package/dist/config/tools.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -4
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +15 -0
- package/dist/launcher.js.map +1 -1
- package/dist/services/aiToolResolver.d.ts.map +1 -1
- package/dist/services/aiToolResolver.js +55 -8
- package/dist/services/aiToolResolver.js.map +1 -1
- package/dist/services/customToolResolver.d.ts.map +1 -1
- package/dist/services/customToolResolver.js +22 -17
- package/dist/services/customToolResolver.js.map +1 -1
- package/dist/utils/prompt.d.ts +12 -0
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js +60 -10
- package/dist/utils/prompt.js.map +1 -1
- package/dist/utils/webui.js +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts +5 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +35 -108
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts +15 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js +57 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.js +103 -0
- package/dist/web/client/src/components/graph/BranchNode.js.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.js +109 -0
- package/dist/web/client/src/components/graph/ClusterNode.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts +17 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js +94 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts +13 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js +113 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts +67 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.js +175 -0
- package/dist/web/client/src/components/graph/graphUtils.js.map +1 -0
- package/dist/web/client/src/components/graph/index.d.ts +10 -0
- package/dist/web/client/src/components/graph/index.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/index.js +10 -0
- package/dist/web/client/src/components/graph/index.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
- package/dist/web/client/src/lib/websocket.js +2 -1
- package/dist/web/client/src/lib/websocket.js.map +1 -1
- package/dist/web/client/vite.config.js +1 -1
- package/dist/web/server/env/importer.d.ts.map +1 -1
- package/dist/web/server/env/importer.js +4 -0
- package/dist/web/server/env/importer.js.map +1 -1
- package/dist/web/server/index.d.ts.map +1 -1
- package/dist/web/server/index.js +9 -0
- package/dist/web/server/index.js.map +1 -1
- package/dist/web/server/pty/manager.d.ts.map +1 -1
- package/dist/web/server/pty/manager.js +24 -1
- package/dist/web/server/pty/manager.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +7 -0
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/tray.d.ts +1 -1
- package/dist/web/server/tray.d.ts.map +1 -1
- package/dist/web/server/tray.js +52 -34
- package/dist/web/server/tray.js.map +1 -1
- package/dist/web/server/websocket/handler.d.ts.map +1 -1
- package/dist/web/server/websocket/handler.js +4 -0
- package/dist/web/server/websocket/handler.js.map +1 -1
- package/package.json +6 -3
- package/src/claude.ts +15 -1
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +2 -1
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +142 -8
- package/src/cli/ui/__tests__/components/App.test.tsx +4 -3
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +1 -0
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +45 -0
- package/src/cli/ui/components/App.tsx +91 -81
- package/src/cli/ui/components/common/Select.tsx +14 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -1
- package/src/cli/ui/types.ts +1 -1
- package/src/config/builtin-tools.ts +3 -0
- package/src/config/tools.ts +24 -1
- package/src/index.ts +50 -3
- package/src/launcher.ts +26 -0
- package/src/services/aiToolResolver.ts +75 -9
- package/src/services/customToolResolver.ts +32 -17
- package/src/utils/__tests__/prompt.test.ts +72 -35
- package/src/utils/prompt.ts +79 -10
- package/src/utils/webui.ts +1 -1
- package/src/web/client/src/components/BranchGraph.tsx +51 -208
- package/src/web/client/src/components/graph/BranchDetailPanel.tsx +152 -0
- package/src/web/client/src/components/graph/BranchNode.tsx +200 -0
- package/src/web/client/src/components/graph/ClusterNode.tsx +211 -0
- package/src/web/client/src/components/graph/SynapticCanvas.tsx +171 -0
- package/src/web/client/src/components/graph/SynapticEdge.tsx +311 -0
- package/src/web/client/src/components/graph/graphUtils.ts +265 -0
- package/src/web/client/src/components/graph/index.ts +10 -0
- package/src/web/client/src/index.css +314 -29
- package/src/web/client/src/lib/websocket.ts +2 -1
- package/src/web/client/vite.config.ts +1 -1
- package/src/web/server/env/importer.ts +5 -0
- package/src/web/server/index.ts +10 -0
- package/src/web/server/pty/manager.ts +43 -1
- package/src/web/server/routes/sessions.ts +15 -0
- package/src/web/server/tray.ts +62 -46
- package/src/web/server/websocket/handler.ts +13 -0
- package/dist/client/assets/index-DsDNCy5f.css +0 -1
- package/dist/client/assets/index-v8smkNOL.js +0 -72
|
@@ -1,89 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ブランチグラフ - シナプス風ビジュアライゼーション
|
|
3
|
+
*
|
|
4
|
+
* React Flow + D3-forceによるインタラクティブなブランチ関係図
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useCallback } from "react";
|
|
3
8
|
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
|
4
9
|
import { Badge } from "@/components/ui/badge";
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
10
|
import type { Branch } from "../../../../types/api.js";
|
|
7
|
-
|
|
8
|
-
const UNKNOWN_BASE = "__unknown__";
|
|
9
|
-
|
|
10
|
-
interface Lane {
|
|
11
|
-
id: string;
|
|
12
|
-
baseLabel: string;
|
|
13
|
-
baseNode: Branch | null;
|
|
14
|
-
nodes: Branch[];
|
|
15
|
-
isSyntheticBase: boolean;
|
|
16
|
-
}
|
|
11
|
+
import { SynapticCanvas, BranchDetailPanel } from "./graph";
|
|
17
12
|
|
|
18
13
|
interface BranchGraphProps {
|
|
19
14
|
branches: Branch[];
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
function formatBranchLabel(branch: Branch): string {
|
|
23
|
-
return branch.name.length > 32
|
|
24
|
-
? `${branch.name.slice(0, 29)}...`
|
|
25
|
-
: branch.name;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getDivergenceLabel(branch: Branch): string {
|
|
29
|
-
if (!branch.divergence) {
|
|
30
|
-
return "divergence: n/a";
|
|
31
|
-
}
|
|
32
|
-
const { ahead, behind, upToDate } = branch.divergence;
|
|
33
|
-
if (upToDate) {
|
|
34
|
-
return "divergence: up-to-date";
|
|
35
|
-
}
|
|
36
|
-
return `divergence: +${ahead} / -${behind}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
17
|
export function BranchGraph({ branches }: BranchGraphProps) {
|
|
40
|
-
const
|
|
41
|
-
return new Map(branches.map((branch) => [branch.name, branch]));
|
|
42
|
-
}, [branches]);
|
|
43
|
-
|
|
44
|
-
const referencedBases = useMemo(() => {
|
|
45
|
-
const baseSet = new Set<string>();
|
|
46
|
-
branches.forEach((branch) => {
|
|
47
|
-
if (branch.baseBranch) {
|
|
48
|
-
baseSet.add(branch.baseBranch);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
return baseSet;
|
|
52
|
-
}, [branches]);
|
|
53
|
-
|
|
54
|
-
const lanes = useMemo<Lane[]>(() => {
|
|
55
|
-
const laneMap = new Map<string, Lane>();
|
|
56
|
-
|
|
57
|
-
branches.forEach((branch) => {
|
|
58
|
-
const base = branch.baseBranch ?? UNKNOWN_BASE;
|
|
18
|
+
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null);
|
|
59
19
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
20
|
+
const handleNodeClick = useCallback((branch: Branch | null) => {
|
|
21
|
+
setSelectedBranch(branch);
|
|
22
|
+
}, []);
|
|
63
23
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
laneMap.set(base, {
|
|
68
|
-
id: base,
|
|
69
|
-
baseLabel: base === UNKNOWN_BASE ? "ベース不明" : base,
|
|
70
|
-
baseNode,
|
|
71
|
-
nodes: [],
|
|
72
|
-
isSyntheticBase: baseNode === null,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
24
|
+
const handlePanelClose = useCallback(() => {
|
|
25
|
+
setSelectedBranch(null);
|
|
26
|
+
}, []);
|
|
75
27
|
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return Array.from(laneMap.values()).sort((a, b) => {
|
|
80
|
-
if (a.id === UNKNOWN_BASE) return 1;
|
|
81
|
-
if (b.id === UNKNOWN_BASE) return -1;
|
|
82
|
-
return a.baseLabel.localeCompare(b.baseLabel, "ja");
|
|
83
|
-
});
|
|
84
|
-
}, [branches, branchMap, referencedBases]);
|
|
85
|
-
|
|
86
|
-
if (!lanes.length) {
|
|
28
|
+
if (!branches.length) {
|
|
87
29
|
return (
|
|
88
30
|
<Card className="border-dashed">
|
|
89
31
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
@@ -99,154 +41,55 @@ export function BranchGraph({ branches }: BranchGraphProps) {
|
|
|
99
41
|
}
|
|
100
42
|
|
|
101
43
|
return (
|
|
102
|
-
<Card>
|
|
44
|
+
<Card className="relative overflow-hidden">
|
|
103
45
|
<CardHeader className="pb-4">
|
|
104
46
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
105
47
|
<div>
|
|
106
48
|
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
107
|
-
|
|
49
|
+
SYNAPTIC GRAPH
|
|
108
50
|
</p>
|
|
109
|
-
<h2 className="mt-1 text-lg font-semibold">
|
|
110
|
-
ベースブランチの関係をグラフィカルに把握
|
|
111
|
-
</h2>
|
|
51
|
+
<h2 className="mt-1 text-lg font-semibold">ブランチネットワーク</h2>
|
|
112
52
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
113
|
-
|
|
114
|
-
upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で派生ノードをレーン表示します。
|
|
53
|
+
クリックでノードを展開・詳細表示。ドラッグで移動、スクロールでズーム。
|
|
115
54
|
</p>
|
|
116
55
|
</div>
|
|
117
56
|
<div className="flex flex-wrap gap-2">
|
|
118
|
-
<Badge variant="outline">
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
57
|
+
<Badge variant="outline" className="flex items-center gap-1">
|
|
58
|
+
<span className="h-2 w-2 rounded-full bg-muted-foreground/30" />
|
|
59
|
+
Cluster
|
|
60
|
+
</Badge>
|
|
61
|
+
<Badge variant="local" className="flex items-center gap-1">
|
|
62
|
+
<span className="h-2 w-2 rounded-full bg-local" />
|
|
63
|
+
Local
|
|
64
|
+
</Badge>
|
|
65
|
+
<Badge variant="remote" className="flex items-center gap-1">
|
|
66
|
+
<span className="h-2 w-2 rounded-full bg-remote" />
|
|
67
|
+
Remote
|
|
68
|
+
</Badge>
|
|
69
|
+
<Badge variant="success" className="flex items-center gap-1">
|
|
70
|
+
<span className="h-2 w-2 rounded-full bg-success" />
|
|
71
|
+
Worktree
|
|
72
|
+
</Badge>
|
|
122
73
|
</div>
|
|
123
74
|
</div>
|
|
124
75
|
</CardHeader>
|
|
125
76
|
|
|
126
|
-
<CardContent className="
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)}
|
|
142
|
-
{lane.isSyntheticBase && (
|
|
143
|
-
<Badge
|
|
144
|
-
variant="outline"
|
|
145
|
-
className="text-xs text-muted-foreground"
|
|
146
|
-
>
|
|
147
|
-
推定のみ
|
|
148
|
-
</Badge>
|
|
149
|
-
)}
|
|
150
|
-
</div>
|
|
151
|
-
<span className="text-sm text-muted-foreground">
|
|
152
|
-
{lane.nodes.length} branch{lane.nodes.length > 1 ? "es" : ""}
|
|
153
|
-
</span>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
<div className="flex flex-wrap gap-2">
|
|
157
|
-
{renderBaseNode(lane)}
|
|
158
|
-
{lane.nodes.map((branch) => (
|
|
159
|
-
<BranchNode key={branch.name} branch={branch} />
|
|
160
|
-
))}
|
|
161
|
-
</div>
|
|
162
|
-
</article>
|
|
163
|
-
))}
|
|
77
|
+
<CardContent className="relative p-0">
|
|
78
|
+
{/* キャンバスコンテナ */}
|
|
79
|
+
<div className="relative h-[500px] w-full">
|
|
80
|
+
<SynapticCanvas
|
|
81
|
+
branches={branches}
|
|
82
|
+
onNodeClick={handleNodeClick}
|
|
83
|
+
className="h-full w-full"
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
{/* 詳細パネル */}
|
|
87
|
+
<BranchDetailPanel
|
|
88
|
+
branch={selectedBranch}
|
|
89
|
+
onClose={handlePanelClose}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
164
92
|
</CardContent>
|
|
165
93
|
</Card>
|
|
166
94
|
);
|
|
167
95
|
}
|
|
168
|
-
|
|
169
|
-
function renderBaseNode(lane: Lane) {
|
|
170
|
-
const label =
|
|
171
|
-
lane.baseLabel === "ベース不明" ? "Unknown base" : lane.baseLabel;
|
|
172
|
-
|
|
173
|
-
const content = (
|
|
174
|
-
<div
|
|
175
|
-
className={cn(
|
|
176
|
-
"group relative rounded-md border bg-card px-3 py-2 transition-colors hover:border-muted-foreground/50",
|
|
177
|
-
lane.baseNode?.type === "local" && "border-l-2 border-l-local",
|
|
178
|
-
lane.baseNode?.type === "remote" && "border-l-2 border-l-remote",
|
|
179
|
-
)}
|
|
180
|
-
>
|
|
181
|
-
<span className="block truncate text-sm font-medium">{label}</span>
|
|
182
|
-
<span className="text-xs text-muted-foreground">BASE</span>
|
|
183
|
-
|
|
184
|
-
{/* Tooltip */}
|
|
185
|
-
<div className="invisible absolute bottom-full left-0 z-10 mb-2 w-48 rounded-md border bg-popover p-2 text-xs shadow-md group-hover:visible">
|
|
186
|
-
<p className="font-medium">{label}</p>
|
|
187
|
-
<p className="text-muted-foreground">
|
|
188
|
-
{lane.baseNode
|
|
189
|
-
? `type: ${lane.baseNode.type}`
|
|
190
|
-
: "推定されたベースブランチ"}
|
|
191
|
-
</p>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
if (lane.baseNode) {
|
|
197
|
-
return (
|
|
198
|
-
<Link
|
|
199
|
-
key={`base-${lane.id}`}
|
|
200
|
-
to={`/${encodeURIComponent(lane.baseNode.name)}`}
|
|
201
|
-
className="block"
|
|
202
|
-
aria-label={`ベースブランチ ${lane.baseNode.name} を開く`}
|
|
203
|
-
>
|
|
204
|
-
{content}
|
|
205
|
-
</Link>
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return <div key={`base-${lane.id}`}>{content}</div>;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function BranchNode({ branch }: { branch: Branch }) {
|
|
213
|
-
const node = (
|
|
214
|
-
<div
|
|
215
|
-
className={cn(
|
|
216
|
-
"group relative rounded-md border bg-card px-3 py-2 transition-colors hover:border-muted-foreground/50",
|
|
217
|
-
branch.type === "local" && "border-l-2 border-l-local",
|
|
218
|
-
branch.type === "remote" && "border-l-2 border-l-remote",
|
|
219
|
-
branch.mergeStatus === "merged" && "opacity-60",
|
|
220
|
-
)}
|
|
221
|
-
>
|
|
222
|
-
<span className="block truncate text-sm font-medium">
|
|
223
|
-
{formatBranchLabel(branch)}
|
|
224
|
-
</span>
|
|
225
|
-
<span className="text-xs text-muted-foreground">
|
|
226
|
-
{branch.worktreePath ? "Worktree" : "No Worktree"}
|
|
227
|
-
</span>
|
|
228
|
-
|
|
229
|
-
{/* Tooltip */}
|
|
230
|
-
<div className="invisible absolute bottom-full left-0 z-10 mb-2 w-56 rounded-md border bg-popover p-2 text-xs shadow-md group-hover:visible">
|
|
231
|
-
<p className="font-medium">{branch.name}</p>
|
|
232
|
-
<p className="text-muted-foreground">
|
|
233
|
-
base: {branch.baseBranch ?? "unknown"}
|
|
234
|
-
</p>
|
|
235
|
-
<p className="text-muted-foreground">{getDivergenceLabel(branch)}</p>
|
|
236
|
-
<p className="text-muted-foreground">
|
|
237
|
-
{branch.worktreePath ?? "Worktree未作成"}
|
|
238
|
-
</p>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
return (
|
|
244
|
-
<Link
|
|
245
|
-
to={`/${encodeURIComponent(branch.name)}`}
|
|
246
|
-
className="block"
|
|
247
|
-
aria-label={`${branch.name} の詳細を開く`}
|
|
248
|
-
>
|
|
249
|
-
{node}
|
|
250
|
-
</Link>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ブランチ詳細パネル
|
|
3
|
+
*
|
|
4
|
+
* ノードクリック時に右側に表示されるサイドパネル
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { Link } from "react-router-dom";
|
|
9
|
+
import { X } from "lucide-react";
|
|
10
|
+
import { Badge } from "@/components/ui/badge";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
Card,
|
|
14
|
+
CardHeader,
|
|
15
|
+
CardContent,
|
|
16
|
+
CardFooter,
|
|
17
|
+
} from "@/components/ui/card";
|
|
18
|
+
import { cn } from "@/lib/utils";
|
|
19
|
+
import type { Branch } from "../../../../../types/api.js";
|
|
20
|
+
|
|
21
|
+
interface BranchDetailPanelProps {
|
|
22
|
+
branch: Branch | null;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BranchDetailPanel({
|
|
28
|
+
branch,
|
|
29
|
+
onClose,
|
|
30
|
+
className,
|
|
31
|
+
}: BranchDetailPanelProps) {
|
|
32
|
+
if (!branch) return null;
|
|
33
|
+
|
|
34
|
+
const hasWorktree = Boolean(branch.worktreePath);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={cn(
|
|
39
|
+
"absolute right-0 top-0 h-full w-80 border-l bg-card/95 backdrop-blur-sm",
|
|
40
|
+
"animate-in slide-in-from-right duration-300",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
<Card className="h-full rounded-none border-0">
|
|
45
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<Badge variant={branch.type === "local" ? "local" : "remote"}>
|
|
48
|
+
{branch.type === "local" ? "Local" : "Remote"}
|
|
49
|
+
</Badge>
|
|
50
|
+
{hasWorktree && <Badge variant="success">Worktree</Badge>}
|
|
51
|
+
</div>
|
|
52
|
+
<Button
|
|
53
|
+
variant="ghost"
|
|
54
|
+
size="icon"
|
|
55
|
+
onClick={onClose}
|
|
56
|
+
className="h-8 w-8"
|
|
57
|
+
>
|
|
58
|
+
<X className="h-4 w-4" />
|
|
59
|
+
</Button>
|
|
60
|
+
</CardHeader>
|
|
61
|
+
|
|
62
|
+
<CardContent className="space-y-4">
|
|
63
|
+
{/* ブランチ名 */}
|
|
64
|
+
<div>
|
|
65
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
66
|
+
Branch Name
|
|
67
|
+
</p>
|
|
68
|
+
<p className="mt-1 break-all font-mono text-sm">{branch.name}</p>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* ベースブランチ */}
|
|
72
|
+
<div>
|
|
73
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
74
|
+
Base Branch
|
|
75
|
+
</p>
|
|
76
|
+
<p className="mt-1 font-mono text-sm">
|
|
77
|
+
{branch.baseBranch ?? "Unknown"}
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* コミットメッセージ */}
|
|
82
|
+
<div>
|
|
83
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
84
|
+
Last Commit
|
|
85
|
+
</p>
|
|
86
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
87
|
+
{branch.commitMessage ?? "No commit message"}
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Divergence */}
|
|
92
|
+
{branch.divergence && (
|
|
93
|
+
<div>
|
|
94
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
95
|
+
Divergence
|
|
96
|
+
</p>
|
|
97
|
+
<div className="mt-2 flex flex-wrap gap-2">
|
|
98
|
+
<Badge variant="outline" className="text-xs">
|
|
99
|
+
↑ {branch.divergence.ahead} ahead
|
|
100
|
+
</Badge>
|
|
101
|
+
<Badge variant="outline" className="text-xs">
|
|
102
|
+
↓ {branch.divergence.behind} behind
|
|
103
|
+
</Badge>
|
|
104
|
+
<Badge
|
|
105
|
+
variant={branch.divergence.upToDate ? "success" : "warning"}
|
|
106
|
+
className="text-xs"
|
|
107
|
+
>
|
|
108
|
+
{branch.divergence.upToDate ? "Up to date" : "Needs sync"}
|
|
109
|
+
</Badge>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{/* Worktree パス */}
|
|
115
|
+
{hasWorktree && (
|
|
116
|
+
<div>
|
|
117
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
118
|
+
Worktree Path
|
|
119
|
+
</p>
|
|
120
|
+
<p className="mt-1 break-all font-mono text-xs text-muted-foreground">
|
|
121
|
+
{branch.worktreePath}
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{/* マージステータス */}
|
|
127
|
+
{branch.mergeStatus && (
|
|
128
|
+
<div>
|
|
129
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
130
|
+
Merge Status
|
|
131
|
+
</p>
|
|
132
|
+
<Badge
|
|
133
|
+
variant={
|
|
134
|
+
branch.mergeStatus === "merged" ? "success" : "outline"
|
|
135
|
+
}
|
|
136
|
+
className="mt-1"
|
|
137
|
+
>
|
|
138
|
+
{branch.mergeStatus}
|
|
139
|
+
</Badge>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</CardContent>
|
|
143
|
+
|
|
144
|
+
<CardFooter className="flex flex-col gap-2">
|
|
145
|
+
<Button asChild className="w-full">
|
|
146
|
+
<Link to={`/${encodeURIComponent(branch.name)}`}>View Details</Link>
|
|
147
|
+
</Button>
|
|
148
|
+
</CardFooter>
|
|
149
|
+
</Card>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ブランチノードコンポーネント
|
|
3
|
+
*
|
|
4
|
+
* ソーマ(神経細胞体)をイメージした生物発光ノード
|
|
5
|
+
* 多層グロー効果と有機的なアニメーションで生命感を表現
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { memo, useMemo } from "react";
|
|
9
|
+
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
import { getNodeColor, getNodeSize, type GraphNode } from "./graphUtils";
|
|
12
|
+
|
|
13
|
+
type BranchNodeProps = NodeProps<GraphNode>;
|
|
14
|
+
|
|
15
|
+
export const BranchNode = memo(function BranchNode({
|
|
16
|
+
data,
|
|
17
|
+
selected,
|
|
18
|
+
}: BranchNodeProps) {
|
|
19
|
+
const { branch } = data;
|
|
20
|
+
|
|
21
|
+
if (!branch) return null;
|
|
22
|
+
|
|
23
|
+
const size = getNodeSize(branch);
|
|
24
|
+
const _baseColor = getNodeColor(branch);
|
|
25
|
+
const hasWorktree = Boolean(branch.worktreePath);
|
|
26
|
+
const isMerged = branch.mergeStatus === "merged";
|
|
27
|
+
|
|
28
|
+
// ブランチ名を短縮
|
|
29
|
+
const displayName = useMemo(() => {
|
|
30
|
+
if (branch.name.length > 18) {
|
|
31
|
+
return `${branch.name.slice(0, 15)}...`;
|
|
32
|
+
}
|
|
33
|
+
return branch.name;
|
|
34
|
+
}, [branch.name]);
|
|
35
|
+
|
|
36
|
+
// ノードカラーをHSL値として抽出(動的スタイル用)
|
|
37
|
+
const colorStyle = useMemo(() => {
|
|
38
|
+
if (hasWorktree) {
|
|
39
|
+
return {
|
|
40
|
+
primary: "hsl(160 90% 45%)",
|
|
41
|
+
glow: "hsl(160 90% 45% / 0.4)",
|
|
42
|
+
inner: "hsl(160 90% 50% / 0.2)",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (branch.type === "local") {
|
|
46
|
+
return {
|
|
47
|
+
primary: "hsl(280 85% 65%)",
|
|
48
|
+
glow: "hsl(280 85% 65% / 0.4)",
|
|
49
|
+
inner: "hsl(280 85% 70% / 0.2)",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
primary: "hsl(200 90% 55%)",
|
|
54
|
+
glow: "hsl(200 90% 55% / 0.4)",
|
|
55
|
+
inner: "hsl(200 90% 60% / 0.2)",
|
|
56
|
+
};
|
|
57
|
+
}, [hasWorktree, branch.type]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{/* 入力ハンドル - デンドライト接続点 */}
|
|
62
|
+
<Handle
|
|
63
|
+
type="target"
|
|
64
|
+
position={Position.Top}
|
|
65
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
66
|
+
style={{
|
|
67
|
+
borderColor: colorStyle.primary,
|
|
68
|
+
boxShadow: `0 0 8px ${colorStyle.glow}`,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
{/* ソーマ本体 */}
|
|
73
|
+
<div
|
|
74
|
+
className={cn(
|
|
75
|
+
"group relative flex cursor-pointer items-center justify-center rounded-full",
|
|
76
|
+
"transition-all duration-500 ease-out",
|
|
77
|
+
hasWorktree && "animate-active-synapse",
|
|
78
|
+
!hasWorktree && "animate-soma-pulse",
|
|
79
|
+
selected && "ring-2 ring-offset-4 ring-offset-background",
|
|
80
|
+
isMerged && "opacity-50",
|
|
81
|
+
)}
|
|
82
|
+
style={{
|
|
83
|
+
width: size,
|
|
84
|
+
height: size,
|
|
85
|
+
background: `radial-gradient(circle at 30% 30%, ${colorStyle.inner}, transparent 60%)`,
|
|
86
|
+
border: `2px solid ${colorStyle.primary}`,
|
|
87
|
+
boxShadow: selected
|
|
88
|
+
? `0 0 30px ${colorStyle.glow}, inset 0 0 20px ${colorStyle.inner}, 0 0 0 4px ${colorStyle.primary}`
|
|
89
|
+
: undefined,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{/* 外殻グロー */}
|
|
93
|
+
<div
|
|
94
|
+
className="absolute -inset-2 rounded-full opacity-30 blur-md"
|
|
95
|
+
style={{ backgroundColor: colorStyle.primary }}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
{/* 核膜 - 内側のリング */}
|
|
99
|
+
<div
|
|
100
|
+
className="absolute inset-2 rounded-full border opacity-40"
|
|
101
|
+
style={{ borderColor: colorStyle.primary }}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
{/* 核 - 中心部 */}
|
|
105
|
+
<div
|
|
106
|
+
className="relative z-10 flex items-center justify-center rounded-full shadow-lg"
|
|
107
|
+
style={{
|
|
108
|
+
width: size * 0.45,
|
|
109
|
+
height: size * 0.45,
|
|
110
|
+
background: `linear-gradient(135deg, ${colorStyle.primary}, ${colorStyle.inner})`,
|
|
111
|
+
boxShadow: `0 0 15px ${colorStyle.glow}`,
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{/* アイコン/タイプ表示 */}
|
|
115
|
+
<span className="text-xs font-bold text-background">
|
|
116
|
+
{branch.type === "local" ? "L" : "R"}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* 細胞小器官 - 装飾ドット */}
|
|
121
|
+
{!isMerged && (
|
|
122
|
+
<>
|
|
123
|
+
<div
|
|
124
|
+
className="absolute h-1.5 w-1.5 rounded-full animate-vesicle-release"
|
|
125
|
+
style={{
|
|
126
|
+
backgroundColor: colorStyle.primary,
|
|
127
|
+
top: "20%",
|
|
128
|
+
right: "25%",
|
|
129
|
+
animationDelay: "0s",
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
<div
|
|
133
|
+
className="absolute h-1 w-1 rounded-full animate-vesicle-release"
|
|
134
|
+
style={{
|
|
135
|
+
backgroundColor: colorStyle.primary,
|
|
136
|
+
bottom: "25%",
|
|
137
|
+
left: "20%",
|
|
138
|
+
animationDelay: "0.7s",
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
</>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{/* Worktree活性インジケータ */}
|
|
145
|
+
{hasWorktree && (
|
|
146
|
+
<div
|
|
147
|
+
className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full animate-synapse-spark"
|
|
148
|
+
style={{
|
|
149
|
+
backgroundColor: "hsl(160 90% 45%)",
|
|
150
|
+
boxShadow: "0 0 10px hsl(160 90% 45% / 0.8)",
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<span className="text-[8px] font-bold text-background">W</span>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
{/* マージ済みオーバーレイ */}
|
|
158
|
+
{isMerged && (
|
|
159
|
+
<div className="absolute inset-0 flex items-center justify-center rounded-full bg-background/60 backdrop-blur-sm">
|
|
160
|
+
<span className="text-[10px] font-medium text-muted-foreground">
|
|
161
|
+
merged
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{/* ホバーツールチップ */}
|
|
167
|
+
<div
|
|
168
|
+
className={cn(
|
|
169
|
+
"absolute -bottom-12 left-1/2 z-50 -translate-x-1/2",
|
|
170
|
+
"rounded-lg border border-border/50 bg-card/95 px-3 py-1.5 backdrop-blur-md",
|
|
171
|
+
"opacity-0 shadow-xl transition-all duration-300",
|
|
172
|
+
"group-hover:opacity-100 group-hover:-translate-y-1",
|
|
173
|
+
)}
|
|
174
|
+
style={{
|
|
175
|
+
boxShadow: `0 4px 20px hsl(220 30% 0% / 0.5), 0 0 20px ${colorStyle.glow}`,
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<p className="whitespace-nowrap text-xs font-medium text-foreground">
|
|
179
|
+
{displayName}
|
|
180
|
+
</p>
|
|
181
|
+
<p className="text-[10px] text-muted-foreground">
|
|
182
|
+
{branch.type === "local" ? "Local" : "Remote"}
|
|
183
|
+
{hasWorktree && " • Active"}
|
|
184
|
+
</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* 出力ハンドル - アクソン接続点 */}
|
|
189
|
+
<Handle
|
|
190
|
+
type="source"
|
|
191
|
+
position={Position.Bottom}
|
|
192
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
193
|
+
style={{
|
|
194
|
+
borderColor: colorStyle.primary,
|
|
195
|
+
boxShadow: `0 0 8px ${colorStyle.glow}`,
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</>
|
|
199
|
+
);
|
|
200
|
+
});
|