diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b2e816af771..6751d82d0e1 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -150,7 +150,7 @@ "@xterm/addon-webgl": "0.20.0-beta.194", "@xterm/headless": "6.1.0-beta.195", "@xterm/xterm": "6.1.0-beta.195", - "@xyflow/react": "^12.10.2", + "@xyflow/react": "^12.10.0", "ai": "^6.0.0", "ansi_up": "^6.0.6", "better-auth": "1.5.6", @@ -183,6 +183,7 @@ "graphql": "^16.13.2", "graphql-language-service-cli": "^3.5.0", "highlight.js": "^11.11.1", + "html-to-image": "^1.11.13", "http-proxy": "^1.18.1", "idb": "^8.0.3", "idb-keyval": "^6.2.2", @@ -225,6 +226,7 @@ "semver": "^7.7.3", "shell-env": "^4.0.3", "shell-quote": "^1.8.3", + "shiki": "^3.21.0", "simple-git": "^3.30.0", "streamdown": "2.5.0", "strip-ansi": "^7.1.2", diff --git a/apps/desktop/src/main/lib/vscode-shim/api/terminal-shim.ts b/apps/desktop/src/main/lib/vscode-shim/api/terminal-shim.ts index f8771ee2a9b..7edaa4ef870 100644 --- a/apps/desktop/src/main/lib/vscode-shim/api/terminal-shim.ts +++ b/apps/desktop/src/main/lib/vscode-shim/api/terminal-shim.ts @@ -54,10 +54,11 @@ function getTerminalManager() { try { // Use dynamic import path that the bundler can resolve // eslint-disable-next-line @typescript-eslint/no-var-requires - // biome-ignore lint: dynamic require for bundler compat const mod = require("../../terminal") as { + // biome-ignore lint/suspicious/noExplicitAny: dynamic require for bundler compat getDaemonTerminalManager: () => any; }; + // biome-ignore lint/suspicious/noExplicitAny: dynamic require for bundler compat return mod.getDaemonTerminalManager() as any; } catch { return null; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatMessageList/ChatMessageList.test.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatMessageList/ChatMessageList.test.tsx index 3604f024066..bc5608cb970 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatMessageList/ChatMessageList.test.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatMessageList/ChatMessageList.test.tsx @@ -330,7 +330,8 @@ describe("ChatMessageList", () => { }); expect(html).toContain("INLINE_SUBAGENT_EXECUTION_MESSAGE"); - expect(html).not.toContain("SUBAGENT_EXECUTION_MESSAGE"); + // Ensure non-inline variant is not rendered (INLINE_ prefix distinguishes them) + expect(html).not.toMatch(/(? {/* Left resize handle */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: resize handle */}
- item.title.toLowerCase().includes(q) || - item.description.toLowerCase().includes(q) || - item.keywords.some((kw) => kw.toLowerCase().includes(q)), - ); + const words = query + .toLowerCase() + .split(/\s+/) + .filter((w) => w.length > 0); + if (words.length === 0) return SETTINGS_ITEMS; + + return SETTINGS_ITEMS.filter((item) => { + const haystack = [ + item.title.toLowerCase(), + item.description.toLowerCase(), + ...item.keywords.map((kw) => kw.toLowerCase()), + ].join(" "); + return words.every((word) => haystack.includes(word)); + }); } export function getMatchCountBySection( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx index f2984005f49..438076b9167 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx @@ -32,7 +32,11 @@ export function PersistentTabRenderer({ if ( paneIds.some((id) => { const type = panes[id]?.type; - return type === "webview" || type === "vscode-extension"; + return ( + type === "webview" || + type === "vscode-extension" || + type === "reference-graph" + ); }) ) { ids.add(tab.id); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPaneInterface/components/ChatMessageList/components/ChatSearch/ChatSearch.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPaneInterface/components/ChatMessageList/components/ChatSearch/ChatSearch.tsx index 6a3f0155f4e..f4bda52a7b7 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPaneInterface/components/ChatMessageList/components/ChatSearch/ChatSearch.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPaneInterface/components/ChatMessageList/components/ChatSearch/ChatSearch.tsx @@ -103,6 +103,7 @@ export function ChatSearch({ style={{ width: `min(${width}px, calc(100% - 4rem))` }} > {/* Left resize handle */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: resize handle */}
; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx index 9e0fb72a8bc..14b86f30871 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx @@ -87,6 +87,10 @@ export function DiffViewerContextMenu({ setValue(_value: string) {}, revealPosition(_line: number, _column?: number) {}, getSelectionLines, + getCursorPosition() { + const sel = getSelectionLines(); + return sel ? { line: sel.startLine, column: 1 } : null; + }, selectAll() { const selection = window.getSelection(); if (!selection) return; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/MarkdownSearch/MarkdownSearch.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/MarkdownSearch/MarkdownSearch.tsx index ac6a72c1b9f..7449bc8177e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/MarkdownSearch/MarkdownSearch.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/MarkdownSearch/MarkdownSearch.tsx @@ -101,6 +101,7 @@ export function MarkdownSearch({ style={{ width: `min(${width}px, calc(100% - 0.5rem))` }} > {/* Left resize handle */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: resize handle */}
(null); + + useEffect(() => { + let cancelled = false; + + async function highlight() { + try { + const html = await highlightCode( + code, + language || "typescript", + shikiTheme, + ); + if (!cancelled) { + setHighlightedHtml(html); + } + } catch { + if (!cancelled) { + setHighlightedHtml(null); + } + } + } + + highlight(); + + return () => { + cancelled = true; + }; + }, [code, language, shikiTheme]); + + const lines = code.split("\n"); + + if (highlightedHtml) { + return ( +
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: shiki output */} +
+
+ ); + } + + // Fallback: plain text with line numbers + return ( +
+
+				
+					{lines.map((line, index) => {
+						const lineNum = startLine + index;
+						return (
+							
+ {lineNum} + + {line || " "} + +
+ ); + })} +
+
+
+ ); +}); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/ReferenceGraphPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/ReferenceGraphPane.tsx index fc26816b6e5..08ba26fa985 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/ReferenceGraphPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/ReferenceGraphPane.tsx @@ -1,7 +1,10 @@ import { Background, + BackgroundVariant, Controls, type Edge, + getNodesBounds, + getViewportForBounds, type Node, ReactFlow, ReactFlowProvider, @@ -11,12 +14,16 @@ import { } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import ELK from "elkjs/lib/elk.bundled.js"; +import { toPng } from "html-to-image"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { MosaicBranch } from "react-mosaic-component"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; +import { useTheme } from "renderer/stores/theme"; +import { createShikiTheme } from "../../../../utils/code-theme/shiki-theme"; import { BasePaneWindow, PaneToolbarActions } from "../components"; import { ReferenceNode } from "./ReferenceNode"; +import "./reference-graph.css"; const elk = new ELK(); @@ -36,27 +43,47 @@ interface ReferenceGraphPaneProps { onPopOut?: () => void; } -const NODE_WIDTH = 350; -const NODE_HEIGHT = 200; +const NODE_MIN_WIDTH = 280; +const NODE_MAX_WIDTH = 500; +const NODE_HEIGHT = 180; +const CHAR_WIDTH = 7.5; +const NODE_PADDING = 40; + +const ELK_OPTIONS = { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.cycleBreaking.strategy": "DEPTH_FIRST", + "elk.spacing.nodeNode": "60", + "elk.layered.spacing.nodeNodeBetweenLayers": "100", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.nodePlacement.favorStraightEdges": "true", + "elk.edgeRouting": "ORTHOGONAL", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.separateConnectedComponents": "true", + "elk.spacing.componentComponent": "80", +}; const nodeTypes = { referenceNode: ReferenceNode }; +function estimateNodeWidth(codeSnippet: string): number { + const lines = codeSnippet.split("\n"); + const maxLineLength = Math.max(...lines.map((line) => line.length)); + const estimatedWidth = maxLineLength * CHAR_WIDTH + NODE_PADDING; + return Math.min(NODE_MAX_WIDTH, Math.max(NODE_MIN_WIDTH, estimatedWidth)); +} + async function layoutGraph( nodes: Node[], edges: Edge[], ): Promise<{ nodes: Node[]; edges: Edge[] }> { const graph = { id: "root", - layoutOptions: { - "elk.algorithm": "layered", - "elk.direction": "DOWN", - "elk.layered.spacing.nodeNodeBetweenLayers": "80", - "elk.spacing.nodeNode": "40", - "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", - }, + layoutOptions: ELK_OPTIONS, children: nodes.map((n) => ({ id: n.id, - width: NODE_WIDTH, + width: estimateNodeWidth( + (n.data as { codeSnippet?: string })?.codeSnippet ?? "", + ), height: NODE_HEIGHT, })), edges: edges.map((e) => ({ @@ -95,6 +122,11 @@ function ReferenceGraphInner({ const pane = useTabsStore((s) => s.panes[paneId]); const refGraphState = pane?.referenceGraph; const { fitView } = useReactFlow(); + const activeTheme = useTheme(); + const shikiTheme = useMemo( + () => (activeTheme ? createShikiTheme(activeTheme) : undefined), + [activeTheme], + ); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -107,6 +139,9 @@ function ReferenceGraphInner({ const mutateAsyncRef = useRef(buildGraphMutation.mutateAsync); mutateAsyncRef.current = buildGraphMutation.mutateAsync; const requestGenerationRef = useRef(0); + const [isExporting, setIsExporting] = useState(false); + const { getNodes } = useReactFlow(); + const graphContainerRef = useRef(null); const addFileViewerPane = useTabsStore((s) => s.addFileViewerPane); @@ -148,6 +183,7 @@ function ReferenceGraphInner({ data: { ...n, onDoubleClick: handleNodeDoubleClick, + shikiTheme, }, })); @@ -155,8 +191,8 @@ function ReferenceGraphInner({ id: e.id, source: e.source, target: e.target, - animated: true, - style: { stroke: "var(--muted-foreground)", strokeWidth: 1.5 }, + type: "smoothstep", + animated: false, })); if (flowNodes.length > 0) { @@ -185,6 +221,7 @@ function ReferenceGraphInner({ workspaceId, maxDepth, handleNodeDoubleClick, + shikiTheme, setNodes, setEdges, fitView, @@ -195,6 +232,66 @@ function ReferenceGraphInner({ void loadGraph(); }, [loadGraph]); + const handleExportPng = useCallback(async () => { + if (isExporting || nodes.length === 0) return; + setIsExporting(true); + + const container = graphContainerRef.current; + const controls = container?.querySelector( + ".react-flow__controls", + ) as HTMLElement | null; + const background = container?.querySelector( + ".react-flow__background", + ) as HTMLElement | null; + + try { + const nodesList = getNodes(); + const nodesBounds = getNodesBounds(nodesList); + const padding = 100; + const imageWidth = nodesBounds.width + padding * 2; + const imageHeight = nodesBounds.height + padding * 2; + const viewport = getViewportForBounds( + nodesBounds, + imageWidth, + imageHeight, + 0.5, + 2, + 0, + ); + + const viewportEl = container?.querySelector( + ".react-flow__viewport", + ) as HTMLElement | null; + if (!viewportEl) return; + + if (controls) controls.style.display = "none"; + if (background) background.style.display = "none"; + + const dataUrl = await toPng(viewportEl, { + backgroundColor: "transparent", + width: imageWidth, + height: imageHeight, + style: { + width: `${imageWidth}px`, + height: `${imageHeight}px`, + transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`, + }, + }); + + // Trigger download + const link = document.createElement("a"); + link.download = `reference-graph-${Date.now()}.png`; + link.href = dataUrl; + link.click(); + } catch (err) { + console.error("[reference-graph] Export PNG failed:", err); + } finally { + if (controls) controls.style.display = ""; + if (background) background.style.display = ""; + setIsExporting(false); + } + }, [isExporting, nodes.length, getNodes]); + const depthOptions = useMemo( () => [1, 2, 3, 4, 5].map((d) => ({ value: d, label: `Depth: ${d}` })), [], @@ -240,6 +337,14 @@ function ReferenceGraphInner({ > {isLoading ? "Loading..." : "Refresh"} +
)} > -
+
{isLoading && nodes.length === 0 && (
@@ -278,7 +383,12 @@ function ReferenceGraphInner({ maxZoom={2} proOptions={{ hideAttribution: true }} > - + void; + shikiTheme?: { + name: string; + type: string; + colors: object; + tokenColors: object[]; + }; } const SYMBOL_ICONS: Record = { - function: "ƒ", - method: "m", - constructor: "C", - class: "◆", - interface: "I", - enum: "E", - variable: "v", - property: "p", - module: "M", - namespace: "N", - type: "T", - constant: "c", - reference: "→", - unknown: "?", - // tsserver symbol kinds (numeric) - "12": "ƒ", // function - "11": "m", // method - "5": "◆", // class - "8": "I", // interface - "10": "E", // enum - "13": "v", // variable - "6": "M", // module + function: "\u{0192}", + method: "\u{1F527}", + constructor: "\u{1F3D7}", + class: "\u{1F3DB}", + interface: "\u{26A1}", + enum: "\u{1F522}", + variable: "\u{1F3B2}", + property: "\u{1F4DD}", + module: "\u{1F4E6}", + namespace: "\u{1F4E6}", + type: "\u{1F4D0}", + constant: "\u{1F511}", + reference: "\u{1F517}", + unknown: "\u{1F4D6}", + // tsserver symbol kinds (string numbers) + "12": "\u{0192}", // function + "11": "\u{1F527}", // method + "5": "\u{1F3DB}", // class + "8": "\u{26A1}", // interface + "10": "\u{1F522}", // enum + "6": "\u{1F4E6}", // module + "13": "\u{1F3B2}", // variable }; function ReferenceNodeComponent({ data }: { data: ReferenceNodeData }) { - const handleDoubleClick = useCallback(() => { + const handleClick = useCallback(() => { data.onDoubleClick(data.absolutePath, data.line); }, [data]); const icon = - SYMBOL_ICONS[data.kind.toLowerCase()] ?? SYMBOL_ICONS[data.kind] ?? "·"; + SYMBOL_ICONS[data.kind.toLowerCase()] ?? + SYMBOL_ICONS[data.kind] ?? + "\u{1F4D6}"; return ( // biome-ignore lint/a11y/noStaticElementInteractions: ReactFlow node wrapper + // biome-ignore lint/a11y/useKeyWithClickEvents: ReactFlow handles keyboard nav
- -
- {icon} - - {data.name} - - {data.isRoot && ( - - ROOT - - )} -
-
- - {data.relativePath ?? data.absolutePath}:{data.line} - + + +
+ {icon} + {data.name}
-
-
-					{data.codeSnippet
-						.split("\n")
-						.slice(0, 8)
-						.map((codeLine, i) => {
-							const lineNum = data.snippetStartLine + i;
-							return (
-								
- - {lineNum} - - {codeLine} -
- ); - })} -
+ +
+ {data.relativePath ?? data.absolutePath}:{data.line}
- + +
); } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/lib/highlighter.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/lib/highlighter.ts new file mode 100644 index 00000000000..3b1628d0d37 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/lib/highlighter.ts @@ -0,0 +1,81 @@ +import { + type BundledLanguage, + createHighlighter, + type Highlighter, +} from "shiki"; + +let highlighterPromise: Promise | null = null; + +const SUPPORTED_LANGUAGES: BundledLanguage[] = [ + "typescript", + "javascript", + "tsx", + "jsx", + "python", + "go", + "rust", + "java", + "c", + "cpp", + "csharp", + "ruby", + "php", + "swift", + "kotlin", + "vue", + "html", + "css", + "json", + "yaml", + "markdown", +]; + +async function getHighlighter(): Promise { + if (!highlighterPromise) { + highlighterPromise = createHighlighter({ + themes: ["dark-plus"], + langs: SUPPORTED_LANGUAGES, + }); + } + return highlighterPromise; +} + +/** + * Highlight code using the app's active theme. + * @param shikiTheme - The theme object from createShikiTheme(). If provided, + * the theme is registered dynamically and used for highlighting. + * Falls back to "dark-plus" if not provided. + */ +export async function highlightCode( + code: string, + language: string, + shikiTheme?: { + name: string; + type: string; + colors: object; + tokenColors: object[]; + }, +): Promise { + const highlighter = await getHighlighter(); + + const safeLanguage = SUPPORTED_LANGUAGES.includes(language as BundledLanguage) + ? (language as BundledLanguage) + : "typescript"; + + let themeName = "dark-plus"; + + if (shikiTheme) { + // Register the app theme dynamically if not already loaded + const loadedThemes = highlighter.getLoadedThemes(); + if (!loadedThemes.includes(shikiTheme.name)) { + // biome-ignore lint/suspicious/noExplicitAny: shiki theme registration accepts dynamic shapes + await highlighter.loadTheme(shikiTheme as any); + } + themeName = shikiTheme.name; + } + + return highlighter.codeToHtml(code, { + lang: safeLanguage, + theme: themeName, + }); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/reference-graph.css b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/reference-graph.css new file mode 100644 index 00000000000..15bc6c40df6 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ReferenceGraphPane/reference-graph.css @@ -0,0 +1,133 @@ +/* Reference Graph node styles — matching reference-graph extension */ + +.ref-graph-node { + background: var(--sidebar, #1e1e1e); + border: 1px solid var(--border, #3c3c3c); + border-radius: 4px; + min-width: 280px; + max-width: 400px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + overflow: hidden; + cursor: pointer; +} + +.ref-graph-node:hover { + border-color: var(--primary, #007fd4); +} + +.ref-graph-node.root { + border-color: var(--primary, #3794ff); + border-width: 2px; +} + +.ref-graph-node-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: var(--muted, #252526); + border-bottom: 1px solid var(--border, #3c3c3c); + font-weight: 600; + font-size: 13px; +} + +.ref-graph-node-icon { + font-size: 14px; + flex-shrink: 0; +} + +.ref-graph-node-name { + color: var(--foreground, #cccccc); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ref-graph-node-location { + padding: 4px 12px; + font-size: 11px; + color: var(--muted-foreground, #8b8b8b); + background: var(--muted, #252526); + border-bottom: 1px solid var(--border, #3c3c3c); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Code preview */ +.ref-graph-code-preview { + padding: 8px 0; + font-family: var(--font-mono, "Consolas", "Courier New", monospace); + font-size: 12px; + line-height: 1.4; + overflow-x: auto; + max-height: 150px; +} + +.ref-graph-code-preview pre { + margin: 0; + padding: 0; +} + +.ref-graph-code-preview code { + display: block; +} + +/* Shiki output styling */ +.ref-graph-code-preview .shiki { + margin: 0; + padding: 4px 0; + background-color: transparent; + font-size: 12px; + line-height: 1.4; +} + +.ref-graph-code-preview .shiki code { + display: block; + font-family: var(--font-mono, "Consolas", "Courier New", monospace); +} + +.ref-graph-code-preview .shiki code .line { + padding: 0 8px; + display: block; +} + +/* Fallback plain text line styling */ +.ref-graph-code-line { + display: flex; + padding: 0 8px; +} + +.ref-graph-code-line-number { + width: 32px; + text-align: right; + padding-right: 12px; + color: var(--muted-foreground, #5a5a5a); + user-select: none; + flex-shrink: 0; + opacity: 0.5; +} + +.ref-graph-code-line-content { + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ReactFlow handles */ +.ref-graph-node .react-flow__handle { + width: 8px; + height: 8px; + background: var(--primary, #3794ff); + border: none; +} + +/* Edge styling */ +.react-flow__edge-path { + stroke: var(--muted-foreground, #8b8b8b); + stroke-width: 2; +} + +.react-flow__edge.selected .react-flow__edge-path { + stroke: var(--primary, #3794ff); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/TerminalSearch.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/TerminalSearch.tsx index 0bc1a3c5f93..84a6c53aa83 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/TerminalSearch.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/TerminalSearch.tsx @@ -160,6 +160,7 @@ export function TerminalSearch({ style={{ width: `min(${width}px, calc(100% - 0.5rem))` }} > {/* Left resize handle */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: resize handle */}
{/* Left resize handle */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: resize handle */}
{ - const trimmedQuery = query.trim(); + const trimmedQuery = query.trim().replace(/^\.\//, ""); if (!trimmedQuery) { return []; }