From 32be69c8ee00b98ef2a000cddff56e79a5c663bb Mon Sep 17 00:00:00 2001 From: jreakin Date: Mon, 23 Mar 2026 04:08:26 -0500 Subject: [PATCH] perf(web): switch remaining components to deep lucide imports, extract ProviderConfigCard - BackendRepoSelector, EmbeddingStatus, Header, MermaidDiagram, QueryFAB, RightPanel, ToolCallCard, WebGPUFallbackDialog: switch from lucide-react barrel imports to @/lib/lucide-icons deep imports - MermaidDiagram: lazy-load ProcessFlowModal via React.lazy - Extract ProviderConfigCard from SettingsPanel for cleaner separation Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/BackendRepoSelector.tsx | 2 +- .../src/components/EmbeddingStatus.tsx | 2 +- gitnexus-web/src/components/Header.tsx | 2 +- .../src/components/MermaidDiagram.tsx | 26 +++-- gitnexus-web/src/components/QueryFAB.tsx | 2 +- gitnexus-web/src/components/RightPanel.tsx | 2 +- gitnexus-web/src/components/ToolCallCard.tsx | 2 +- .../src/components/WebGPUFallbackDialog.tsx | 2 +- .../settings/ProviderConfigCard.tsx | 110 ++++++++++++++++++ 9 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 gitnexus-web/src/components/settings/ProviderConfigCard.tsx diff --git a/gitnexus-web/src/components/BackendRepoSelector.tsx b/gitnexus-web/src/components/BackendRepoSelector.tsx index 7e158c4b14..4eb107f0b3 100644 --- a/gitnexus-web/src/components/BackendRepoSelector.tsx +++ b/gitnexus-web/src/components/BackendRepoSelector.tsx @@ -1,4 +1,4 @@ -import { Server, ArrowRight } from 'lucide-react'; +import { Server, ArrowRight } from '@/lib/lucide-icons'; import { BackendRepo } from '../services/backend'; interface BackendRepoSelectorProps { diff --git a/gitnexus-web/src/components/EmbeddingStatus.tsx b/gitnexus-web/src/components/EmbeddingStatus.tsx index 4bf6001c90..a7c051ab1a 100644 --- a/gitnexus-web/src/components/EmbeddingStatus.tsx +++ b/gitnexus-web/src/components/EmbeddingStatus.tsx @@ -1,4 +1,4 @@ -import { Brain, Loader2, Check, AlertCircle, Zap, FlaskConical } from 'lucide-react'; +import { Brain, Loader2, Check, AlertCircle, Zap, FlaskConical } from '@/lib/lucide-icons'; import { useAppState } from '../hooks/useAppState'; import { useState } from 'react'; import { WebGPUFallbackDialog } from './WebGPUFallbackDialog'; diff --git a/gitnexus-web/src/components/Header.tsx b/gitnexus-web/src/components/Header.tsx index e8a7fc1623..e9b936aa50 100644 --- a/gitnexus-web/src/components/Header.tsx +++ b/gitnexus-web/src/components/Header.tsx @@ -1,4 +1,4 @@ -import { Search, Settings, HelpCircle, Sparkles, Github, Star, ChevronDown } from 'lucide-react'; +import { Search, Settings, HelpCircle, Sparkles, Github, Star, ChevronDown } from '@/lib/lucide-icons'; import { useAppState } from '../hooks/useAppState'; import type { RepoSummary } from '../services/server-connection'; import { useState, useMemo, useRef, useEffect, useCallback } from 'react'; diff --git a/gitnexus-web/src/components/MermaidDiagram.tsx b/gitnexus-web/src/components/MermaidDiagram.tsx index 5a0598b25e..447dfe3e72 100644 --- a/gitnexus-web/src/components/MermaidDiagram.tsx +++ b/gitnexus-web/src/components/MermaidDiagram.tsx @@ -1,10 +1,13 @@ -import { useEffect, useRef, useState } from 'react'; +import { Suspense, useEffect, useRef, useState, lazy } from 'react'; import mermaid from 'mermaid'; import DOMPurify from 'dompurify'; -import { AlertTriangle, Maximize2 } from 'lucide-react'; -import { ProcessFlowModal } from './ProcessFlowModal'; +import { AlertTriangle, Maximize2 } from '@/lib/lucide-icons'; import type { ProcessData } from '../lib/mermaid-generator'; +const ProcessFlowModal = lazy(() => + import('./ProcessFlowModal').then((m) => ({ default: m.ProcessFlowModal })), +); + // Initialize mermaid with cyan theme matching ProcessFlowModal mermaid.initialize({ startOnLoad: false, @@ -68,7 +71,8 @@ export const MermaidDiagram = ({ code }: MermaidDiagramProps) => { // Render the diagram const { svg: renderedSvg } = await mermaid.render(id, code.trim()); - setSvg(renderedSvg); + const sanitizedSvg = DOMPurify.sanitize(renderedSvg, { USE_PROFILES: { svg: true, svgFilters: true } }); + setSvg(sanitizedSvg); setError(null); } catch (err) { // Silent catch for streaming: @@ -148,10 +152,16 @@ export const MermaidDiagram = ({ code }: MermaidDiagramProps) => { {/* Use ProcessFlowModal for expansion */} {showModal && processData && ( - setShowModal(false)} - /> + Loading diagram… + } + > + setShowModal(false)} + /> + )} ); diff --git a/gitnexus-web/src/components/QueryFAB.tsx b/gitnexus-web/src/components/QueryFAB.tsx index ccb3c10e92..c8be4c8768 100644 --- a/gitnexus-web/src/components/QueryFAB.tsx +++ b/gitnexus-web/src/components/QueryFAB.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect, useCallback } from 'react'; -import { Terminal, Play, X, ChevronDown, ChevronUp, Loader2, Sparkles, Table } from 'lucide-react'; +import { Terminal, Play, X, ChevronDown, ChevronUp, Loader2, Sparkles, Table } from '@/lib/lucide-icons'; import { useAppState } from '../hooks/useAppState'; const EXAMPLE_QUERIES = [ diff --git a/gitnexus-web/src/components/RightPanel.tsx b/gitnexus-web/src/components/RightPanel.tsx index 84be0f7385..b806240fcf 100644 --- a/gitnexus-web/src/components/RightPanel.tsx +++ b/gitnexus-web/src/components/RightPanel.tsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useCallback } from 'react'; import { Send, Square, Sparkles, User, PanelRightClose, Loader2, AlertTriangle, GitBranch -} from 'lucide-react'; +} from '@/lib/lucide-icons'; import { useAppState } from '../hooks/useAppState'; import { ToolCallCard } from './ToolCallCard'; import { isProviderConfigured } from '../core/llm/settings-service'; diff --git a/gitnexus-web/src/components/ToolCallCard.tsx b/gitnexus-web/src/components/ToolCallCard.tsx index e202f83d6b..685b2b8a80 100644 --- a/gitnexus-web/src/components/ToolCallCard.tsx +++ b/gitnexus-web/src/components/ToolCallCard.tsx @@ -6,7 +6,7 @@ */ import { useState } from 'react'; -import { ChevronDown, ChevronRight, Sparkles, Check, Loader2, AlertCircle } from 'lucide-react'; +import { ChevronDown, ChevronRight, Sparkles, Check, Loader2, AlertCircle } from '@/lib/lucide-icons'; import type { ToolCallInfo } from '../core/llm/types'; interface ToolCallCardProps { diff --git a/gitnexus-web/src/components/WebGPUFallbackDialog.tsx b/gitnexus-web/src/components/WebGPUFallbackDialog.tsx index 8bb28d9c45..8868d7ff72 100644 --- a/gitnexus-web/src/components/WebGPUFallbackDialog.tsx +++ b/gitnexus-web/src/components/WebGPUFallbackDialog.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { X, Snail, Rocket, SkipForward } from 'lucide-react'; +import { X, Snail, Rocket, SkipForward } from '@/lib/lucide-icons'; interface WebGPUFallbackDialogProps { isOpen: boolean; diff --git a/gitnexus-web/src/components/settings/ProviderConfigCard.tsx b/gitnexus-web/src/components/settings/ProviderConfigCard.tsx new file mode 100644 index 0000000000..69d9207fa9 --- /dev/null +++ b/gitnexus-web/src/components/settings/ProviderConfigCard.tsx @@ -0,0 +1,110 @@ +import { ReactNode } from 'react'; +import { Eye, EyeOff, Key } from '@/lib/lucide-icons'; + +type ApiKeyField = { + value: string; + placeholder: string; + helperText?: string; + helperLink?: string; + helperLinkLabel?: string; + isVisible: boolean; + onChange: (value: string) => void; + onToggleVisibility: () => void; +}; + +type ModelField = { + value: string; + placeholder: string; + label?: string; + helperText?: string; + onChange: (value: string) => void; +}; + +interface ProviderConfigCardProps { + title: string; + description?: string; + apiKey?: ApiKeyField; + model?: ModelField; + children?: ReactNode; +} + +export const ProviderConfigCard = ({ + title, + description, + apiKey, + model, + children, +}: ProviderConfigCardProps) => { + return ( +
+
+
+

{title}

+ {description ? ( +

{description}

+ ) : null} +
+
+ + {apiKey && ( +
+ +
+ apiKey.onChange(e.target.value)} + placeholder={apiKey.placeholder} + className="w-full px-4 py-3 pr-12 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all" + /> + +
+ {apiKey.helperText && ( +

+ {apiKey.helperText}{' '} + {apiKey.helperLink ? ( + + {apiKey.helperLinkLabel ?? 'Learn more'} + + ) : null} +

+ )} +
+ )} + + {model && ( +
+ + model.onChange(e.target.value)} + placeholder={model.placeholder} + className="w-full px-4 py-3 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all font-mono text-sm" + /> + {model.helperText ? ( +

{model.helperText}

+ ) : null} +
+ )} + + {children} +
+ ); +};