From bca1d07fd542bea9b1738c719521ad4ef10cfb29 Mon Sep 17 00:00:00 2001 From: haharimoto Date: Fri, 22 May 2026 05:03:22 +0900 Subject: [PATCH 1/2] added a tooltip for each node showing details about the node --- .env.example | 11 -- bun.lock | 20 +-- frontend/components/graph/GraphCanvas.tsx | 158 ++++++++++++++++++++-- 3 files changed, 153 insertions(+), 36 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 47b4040..0000000 --- a/.env.example +++ /dev/null @@ -1,11 +0,0 @@ -PORT=3000 -REPO_TEMP_DIR=./tmp/repos - -DEVLENS_LLM_PROVIDER = summarization.provider -DEVLENS_LLM_MODEL = summarization.model -DEVLENS_LLM_KEY = summarization.apiKey -DEVLENS_LLM_BASE_URL = summarization.baseUrl (Ollama in Docker) -DEVLENS_BATCH_SIZE = summarization.batchSize - - -OLLAMA_PING_URL = http://localhost:11434 \ No newline at end of file diff --git a/bun.lock b/bun.lock index 8e7675b..f28d5f0 100644 --- a/bun.lock +++ b/bun.lock @@ -279,7 +279,7 @@ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], "@kwsites/file-exists": ["@kwsites/file-exists@1.1.1", "", { "dependencies": { "debug": "^4.1.1" } }, "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw=="], @@ -1465,10 +1465,10 @@ "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], @@ -1487,18 +1487,8 @@ "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - "@jest/reporters/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@jest/reporters/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - "@jest/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@jest/transform/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -1551,8 +1541,6 @@ "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "istanbul-lib-source-maps/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "jest-config/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "jest-config/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], @@ -1587,8 +1575,6 @@ "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - "v8-to-istanbul/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], diff --git a/frontend/components/graph/GraphCanvas.tsx b/frontend/components/graph/GraphCanvas.tsx index e090700..513e591 100644 --- a/frontend/components/graph/GraphCanvas.tsx +++ b/frontend/components/graph/GraphCanvas.tsx @@ -1,5 +1,5 @@ import { CodeEdge, CodeNode } from "@/lib/types"; -import { useEffect, useRef, useImperativeHandle, forwardRef } from "react"; +import { useEffect, useRef, useImperativeHandle, forwardRef, useState } from "react"; import cytoscape from "cytoscape"; import fcose from "cytoscape-fcose"; import { toElements } from "./cytoscopeUtils"; @@ -7,6 +7,117 @@ import { getCytoscapeStyles, getLayoutConfig } from "./cytoscapeConfig"; cytoscape.use(fcose); +// ─── Tooltip ────────────────────────────────────────────────────────────────── + +interface TooltipInfo { + x: number; + y: number; + type: string; + filePath: string; + score: number; + hasState: boolean; + hooks: string[]; + children: string[]; +} + +const TOOLTIP_TYPE_COLORS: Record = { + COMPONENT: { bg: "#2dd4bf18", text: "#2dd4bf", border: "#2dd4bf30" }, + HOOK: { bg: "#c084fc18", text: "#c084fc", border: "#c084fc30" }, + FUNCTION: { bg: "#60a5fa18", text: "#60a5fa", border: "#60a5fa30" }, + STATE_STORE: { bg: "#fb923c18", text: "#fb923c", border: "#fb923c30" }, + UTILITY: { bg: "#94a3b818", text: "#94a3b8", border: "#94a3b830" }, + FILE: { bg: "#f472b618", text: "#f472b6", border: "#f472b630" }, + GHOST: { bg: "#6b728018", text: "#6b7280", border: "#6b728030" }, + ROUTE: { bg: "#818cf818", text: "#818cf8", border: "#818cf830" }, + TEST: { bg: "#f9731618", text: "#f97316", border: "#f9731630" }, + STORY: { bg: "#a78bfa18", text: "#a78bfa", border: "#a78bfa30" }, +}; + +function NodeTooltip({ t }: { t: TooltipInfo }) { + const colors = TOOLTIP_TYPE_COLORS[t.type] ?? { bg: "#21262d", text: "#8b949e", border: "#30363d" }; + const shortPath = t.filePath.split("/").slice(-3).join("/"); + const hasExtra = t.hasState || t.hooks.length > 0 || t.children.length > 0; + + return ( +
+
+ {/* Type badge */} + + {t.type.replace("_", " ")} + + + {/* File path */} +
+ {shortPath} +
+ + {/* Divider */} + {hasExtra && ( +
+ )} + + {/* Hooks */} + {t.hooks.length > 0 && ( +
+ {t.hooks.slice(0, 4).join(", ")}{t.hooks.length > 4 ? " …" : ""} +
+ )} + + {/* Local state pill */} + {t.hasState && ( +
+ + Local state +
+ )} + + {/* Direct children */} + {t.children.length > 0 && ( +
+
+ Children +
+
+ {t.children.slice(0, 8).map((name) => ( + + {name} + + ))} + {t.children.length > 8 && ( + + +{t.children.length - 8} more + + )} +
+
+ )} +
+
+ ); +} + // ─── Exposed handle ─────────────────────────────────────────────────────────── export interface GraphCanvasHandle { @@ -30,6 +141,7 @@ const GraphCanvas = forwardRef( ({ nodes, edges, onNodeClick }, ref) => { const containerRef = useRef(null); const cyRef = useRef(null); + const [tooltip, setTooltip] = useState(null); useImperativeHandle(ref, () => ({ focusNode(nodeId: string) { @@ -148,8 +260,35 @@ const GraphCanvas = forwardRef( e.target.style("z-index", 9999); onNodeClick?.(e.target.id()); }); - cy.on("mouseover", "node", (e) => e.target.addClass("hover")); - cy.on("mouseout", "node", (e) => e.target.removeClass("hover")); + cy.on("mouseover", "node", (e) => { + e.target.addClass("hover"); + const pos = e.target.renderedPosition() as { x: number; y: number }; + const meta = e.target.data("metadata") as Record | undefined; + const hooks = Array.isArray(meta?.hooks) ? (meta!.hooks as string[]) : []; + + const children: string[] = []; + e.target.outgoers("edge").forEach((edge: cytoscape.EdgeSingular) => { + if (edge.data("type") === "PROP_PASS") { + children.push(edge.target().data("label") as string); + } + }); + + setTooltip({ + x: pos.x, + y: pos.y, + type: e.target.data("type") as string, + filePath: e.target.data("filePath") as string, + score: e.target.data("score") as number, + hasState: !!(meta?.hasState), + hooks, + children, + }); + }); + cy.on("mouseout", "node", (e) => { + e.target.removeClass("hover"); + setTooltip(null); + }); + cy.on("pan zoom", () => setTooltip(null)); cy.on("tap", (e) => { if (e.target === cy) { cy.nodes().style("z-index", 1); @@ -164,11 +303,14 @@ const GraphCanvas = forwardRef( }, []); return ( -
+
+
+ {tooltip && } +
); }, ); From 55f91409c3a00fed073fa80316aa14ddb995a278 Mon Sep 17 00:00:00 2001 From: haharimoto Date: Fri, 5 Jun 2026 17:40:50 +0900 Subject: [PATCH 2/2] restored deleted file --- .env.example | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..47b4040 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +PORT=3000 +REPO_TEMP_DIR=./tmp/repos + +DEVLENS_LLM_PROVIDER = summarization.provider +DEVLENS_LLM_MODEL = summarization.model +DEVLENS_LLM_KEY = summarization.apiKey +DEVLENS_LLM_BASE_URL = summarization.baseUrl (Ollama in Docker) +DEVLENS_BATCH_SIZE = summarization.batchSize + + +OLLAMA_PING_URL = http://localhost:11434 \ No newline at end of file