@akiojin/gwt 4.2.0 → 4.3.1
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/dist/claude.d.ts +2 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +49 -2
- 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/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 +2 -1
- 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/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 +5 -2
- package/src/claude.ts +57 -2
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +104 -0
- package/src/cli/ui/components/App.tsx +91 -81
- 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 +3 -1
- package/src/launcher.ts +26 -0
- package/src/services/aiToolResolver.ts +75 -9
- package/src/services/customToolResolver.ts +32 -17
- 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,12 +1,16 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
import { platform } from "os";
|
|
3
3
|
import { getToolById } from "../config/tools.js";
|
|
4
|
+
import { CLAUDE_CODE_TOOL } from "../config/builtin-tools.js";
|
|
4
5
|
import {
|
|
5
6
|
CODEX_DEFAULT_ARGS,
|
|
6
7
|
CLAUDE_PERMISSION_SKIP_ARGS,
|
|
7
8
|
} from "../shared/aiToolConstants.js";
|
|
8
9
|
import { prepareCustomToolExecution } from "./customToolResolver.js";
|
|
9
10
|
import type { LaunchOptions } from "../types/tools.js";
|
|
11
|
+
import { createLogger } from "../logging/logger.js";
|
|
12
|
+
|
|
13
|
+
const logger = createLogger({ category: "resolver" });
|
|
10
14
|
|
|
11
15
|
const DETECTION_COMMAND = platform() === "win32" ? "where" : "which";
|
|
12
16
|
const MIN_BUN_MAJOR = 1;
|
|
@@ -41,12 +45,32 @@ export class AIToolResolutionError extends Error {
|
|
|
41
45
|
async function commandExists(command: string): Promise<boolean> {
|
|
42
46
|
try {
|
|
43
47
|
await execa(DETECTION_COMMAND, [command], { shell: true });
|
|
48
|
+
logger.debug({ command, exists: true }, "Command check");
|
|
44
49
|
return true;
|
|
45
50
|
} catch {
|
|
51
|
+
logger.debug({ command, exists: false }, "Command check");
|
|
46
52
|
return false;
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
/**
|
|
57
|
+
* コマンドのフルパスを取得
|
|
58
|
+
* node-ptyはシェルを経由しないため、フルパスが必要
|
|
59
|
+
*/
|
|
60
|
+
async function resolveCommandPath(command: string): Promise<string | null> {
|
|
61
|
+
try {
|
|
62
|
+
const { stdout } = await execa(DETECTION_COMMAND, [command], {
|
|
63
|
+
shell: true,
|
|
64
|
+
});
|
|
65
|
+
const fullPath = stdout.trim().split("\n")[0];
|
|
66
|
+
logger.debug({ command, fullPath }, "Command path resolved");
|
|
67
|
+
return fullPath || null;
|
|
68
|
+
} catch {
|
|
69
|
+
logger.debug({ command, fullPath: null }, "Command path resolution failed");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
50
74
|
let bunxCheckPromise: Promise<void> | null = null;
|
|
51
75
|
|
|
52
76
|
async function ensureBunxAvailable(): Promise<void> {
|
|
@@ -67,6 +91,7 @@ async function ensureBunxAvailable(): Promise<void> {
|
|
|
67
91
|
try {
|
|
68
92
|
const { stdout } = await execa("bun", ["--version"]);
|
|
69
93
|
const version = stdout.trim();
|
|
94
|
+
logger.debug({ bunVersion: version }, "Bun version detected");
|
|
70
95
|
const major = parseInt(version.split(".")[0] ?? "0", 10);
|
|
71
96
|
if (!Number.isFinite(major) || major < MIN_BUN_MAJOR) {
|
|
72
97
|
throw new AIToolResolutionError(
|
|
@@ -144,20 +169,40 @@ export async function resolveClaudeCommand(
|
|
|
144
169
|
options: ClaudeCommandOptions = {},
|
|
145
170
|
): Promise<ResolvedCommand> {
|
|
146
171
|
const args = buildClaudeArgs(options);
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
const envOverrides = CLAUDE_CODE_TOOL.env
|
|
173
|
+
? { env: { ...CLAUDE_CODE_TOOL.env } as NodeJS.ProcessEnv }
|
|
174
|
+
: {};
|
|
175
|
+
|
|
176
|
+
// フルパスを取得(node-ptyはシェルを経由しないため必要)
|
|
177
|
+
const claudePath = await resolveCommandPath("claude");
|
|
178
|
+
if (claudePath) {
|
|
179
|
+
logger.info(
|
|
180
|
+
{ command: claudePath, usesFallback: false },
|
|
181
|
+
"Claude command resolved",
|
|
182
|
+
);
|
|
149
183
|
return {
|
|
150
|
-
command:
|
|
184
|
+
command: claudePath,
|
|
151
185
|
args,
|
|
152
186
|
usesFallback: false,
|
|
187
|
+
...envOverrides,
|
|
153
188
|
};
|
|
154
189
|
}
|
|
155
190
|
|
|
156
|
-
|
|
191
|
+
// bunxへフォールバック
|
|
192
|
+
const bunxPath = await resolveCommandPath("bunx");
|
|
193
|
+
if (!bunxPath) {
|
|
194
|
+
await ensureBunxAvailable(); // エラーをスローする
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
logger.info(
|
|
198
|
+
{ command: bunxPath ?? "bunx", usesFallback: true },
|
|
199
|
+
"Claude command resolved (fallback)",
|
|
200
|
+
);
|
|
157
201
|
return {
|
|
158
|
-
command: "bunx",
|
|
202
|
+
command: bunxPath ?? "bunx",
|
|
159
203
|
args: [CLAUDE_CLI_PACKAGE, ...args],
|
|
160
204
|
usesFallback: true,
|
|
205
|
+
...envOverrides,
|
|
161
206
|
};
|
|
162
207
|
}
|
|
163
208
|
|
|
@@ -198,17 +243,32 @@ export async function resolveCodexCommand(
|
|
|
198
243
|
): Promise<ResolvedCommand> {
|
|
199
244
|
const args = buildCodexArgs(options);
|
|
200
245
|
|
|
201
|
-
|
|
246
|
+
// フルパスを取得(node-ptyはシェルを経由しないため必要)
|
|
247
|
+
const codexPath = await resolveCommandPath("codex");
|
|
248
|
+
if (codexPath) {
|
|
249
|
+
logger.info(
|
|
250
|
+
{ command: codexPath, usesFallback: false },
|
|
251
|
+
"Codex command resolved",
|
|
252
|
+
);
|
|
202
253
|
return {
|
|
203
|
-
command:
|
|
254
|
+
command: codexPath,
|
|
204
255
|
args,
|
|
205
256
|
usesFallback: false,
|
|
206
257
|
};
|
|
207
258
|
}
|
|
208
259
|
|
|
209
|
-
|
|
260
|
+
// bunxへフォールバック
|
|
261
|
+
const bunxPath = await resolveCommandPath("bunx");
|
|
262
|
+
if (!bunxPath) {
|
|
263
|
+
await ensureBunxAvailable(); // エラーをスローする
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
logger.info(
|
|
267
|
+
{ command: bunxPath ?? "bunx", usesFallback: true },
|
|
268
|
+
"Codex command resolved (fallback)",
|
|
269
|
+
);
|
|
210
270
|
return {
|
|
211
|
-
command: "bunx",
|
|
271
|
+
command: bunxPath ?? "bunx",
|
|
212
272
|
args: [CODEX_CLI_PACKAGE, ...args],
|
|
213
273
|
usesFallback: true,
|
|
214
274
|
};
|
|
@@ -223,6 +283,7 @@ export async function resolveCustomToolCommand(
|
|
|
223
283
|
): Promise<ResolvedCommand> {
|
|
224
284
|
const tool = await getToolById(options.toolId);
|
|
225
285
|
if (!tool) {
|
|
286
|
+
logger.error({ toolId: options.toolId }, "Custom tool not found");
|
|
226
287
|
throw new AIToolResolutionError(
|
|
227
288
|
"CUSTOM_TOOL_NOT_FOUND",
|
|
228
289
|
`Custom tool not found: ${options.toolId}`,
|
|
@@ -235,6 +296,11 @@ export async function resolveCustomToolCommand(
|
|
|
235
296
|
|
|
236
297
|
const execution = await prepareCustomToolExecution(tool, options);
|
|
237
298
|
|
|
299
|
+
logger.info(
|
|
300
|
+
{ toolId: options.toolId, command: execution.command },
|
|
301
|
+
"Custom tool command resolved",
|
|
302
|
+
);
|
|
303
|
+
|
|
238
304
|
return {
|
|
239
305
|
command: execution.command,
|
|
240
306
|
args: execution.args,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
import type { CustomAITool, LaunchOptions } from "../types/tools.js";
|
|
3
|
+
import { createLogger } from "../logging/logger.js";
|
|
4
|
+
|
|
5
|
+
const logger = createLogger({ category: "custom-resolver" });
|
|
3
6
|
|
|
4
7
|
export interface CustomToolExecutionPlan {
|
|
5
8
|
command: string;
|
|
@@ -21,6 +24,7 @@ export async function resolveCommandPath(commandName: string): Promise<string> {
|
|
|
21
24
|
);
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
logger.debug({ commandName, resolvedPath }, "Command path resolved");
|
|
24
28
|
return resolvedPath;
|
|
25
29
|
} catch (error) {
|
|
26
30
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -55,6 +59,10 @@ export function buildCustomToolArgs(
|
|
|
55
59
|
args.push(...options.extraArgs);
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
logger.debug(
|
|
63
|
+
{ toolId: tool.id, argsCount: args.length },
|
|
64
|
+
"Custom tool args built",
|
|
65
|
+
);
|
|
58
66
|
return args;
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -62,37 +70,44 @@ export async function prepareCustomToolExecution(
|
|
|
62
70
|
tool: CustomAITool,
|
|
63
71
|
options: LaunchOptions = {},
|
|
64
72
|
): Promise<CustomToolExecutionPlan> {
|
|
65
|
-
const
|
|
73
|
+
const baseArgs = buildCustomToolArgs(tool, options);
|
|
66
74
|
const envOverrides: NodeJS.ProcessEnv | undefined = tool.env
|
|
67
75
|
? ({ ...tool.env } as NodeJS.ProcessEnv)
|
|
68
76
|
: undefined;
|
|
69
77
|
|
|
78
|
+
let command: string;
|
|
79
|
+
let args: string[];
|
|
80
|
+
|
|
70
81
|
switch (tool.type) {
|
|
71
82
|
case "path": {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
...(envOverrides ? { env: envOverrides } : {}),
|
|
76
|
-
};
|
|
83
|
+
command = tool.command;
|
|
84
|
+
args = baseArgs;
|
|
85
|
+
break;
|
|
77
86
|
}
|
|
78
87
|
case "bunx": {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
...(envOverrides ? { env: envOverrides } : {}),
|
|
83
|
-
};
|
|
88
|
+
command = "bunx";
|
|
89
|
+
args = [tool.command, ...baseArgs];
|
|
90
|
+
break;
|
|
84
91
|
}
|
|
85
92
|
case "command": {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
args,
|
|
90
|
-
...(envOverrides ? { env: envOverrides } : {}),
|
|
91
|
-
};
|
|
93
|
+
command = await resolveCommandPath(tool.command);
|
|
94
|
+
args = baseArgs;
|
|
95
|
+
break;
|
|
92
96
|
}
|
|
93
97
|
default: {
|
|
94
98
|
const exhaustive: never = tool.type;
|
|
95
99
|
throw new Error(`Unknown custom tool type: ${exhaustive as string}`);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
102
|
+
|
|
103
|
+
logger.debug(
|
|
104
|
+
{ toolId: tool.id, toolType: tool.type, command, hasEnv: !!envOverrides },
|
|
105
|
+
"Custom tool execution prepared",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
command,
|
|
110
|
+
args,
|
|
111
|
+
...(envOverrides ? { env: envOverrides } : {}),
|
|
112
|
+
};
|
|
98
113
|
}
|
package/src/utils/webui.ts
CHANGED
|
@@ -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
|
-
}
|