From a8843a599eda2bc8550197cff88646f6573368ec Mon Sep 17 00:00:00 2001 From: "B. Lawrence Sharma" Date: Mon, 23 Mar 2026 03:25:01 +0530 Subject: [PATCH] =?UTF-8?q?feat(ui):=20ship=20HelpPanel=20=E2=80=94=20beca?= =?UTF-8?q?use=20great=20tools=20don't=20make=20you=20google=20how=20to=20?= =?UTF-8?q?use=20them=20(#465)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove friction in onboarding by correcting typo * Revert "remove friction in onboarding by correcting typo" This reverts commit 07dec38c2c8781328beb02410fcca834342e3402. * feat(ui): add HelpPanel component with tabbed reference, node legend, AI query guide, and dual Mac/Windows keyboard shortcuts * feat(ui): add HelpPanel component with tabbed reference, node legend, AI query guide, and dual Mac/Windows keyboard shortcuts * made changes based on the suggestions * minor fix --- gitnexus-web/src/App.tsx | 11 + gitnexus-web/src/components/Header.tsx | 10 +- gitnexus-web/src/components/HelpPanel.tsx | 390 ++++++++++++++++++++++ gitnexus-web/src/hooks/useAppState.tsx | 5 + 4 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 gitnexus-web/src/components/HelpPanel.tsx diff --git a/gitnexus-web/src/App.tsx b/gitnexus-web/src/App.tsx index 3233ecd01e..665a7f30db 100644 --- a/gitnexus-web/src/App.tsx +++ b/gitnexus-web/src/App.tsx @@ -13,6 +13,7 @@ import { FileEntry } from './services/zip'; import { getActiveProviderConfig } from './core/llm/settings-service'; import { createKnowledgeGraph } from './core/graph/graph'; import { connectToServer, fetchRepos, normalizeServerUrl, type ConnectToServerResult } from './services/server-connection'; +import { HelpPanel } from './components/HelpPanel'; const AppContent = () => { const { @@ -29,6 +30,8 @@ const AppContent = () => { hydrateServerGraph, isSettingsPanelOpen, setSettingsPanelOpen, + isHelpDialogBoxOpen, + setHelpDialogBoxOpen, refreshLLMSettings, initializeAgent, startEmbeddings, @@ -42,6 +45,7 @@ const AppContent = () => { setAvailableRepos, switchRepo, hydrateWorkerFromServer, + graph } = useAppState(); const graphCanvasRef = useRef(null); @@ -317,6 +321,13 @@ const AppContent = () => { onSettingsSaved={handleSettingsSaved} /> + setHelpDialogBoxOpen(false)} + nodeCount={graph!.nodes.length} + edgeCount={graph!.relationships.length} + /> + ); }; diff --git a/gitnexus-web/src/components/Header.tsx b/gitnexus-web/src/components/Header.tsx index 64edd0077b..e8a7fc1623 100644 --- a/gitnexus-web/src/components/Header.tsx +++ b/gitnexus-web/src/components/Header.tsx @@ -32,6 +32,7 @@ export const Header = ({ onFocusNode, availableRepos = [], onSwitchRepo }: Heade isRightPanelOpen, rightPanelTab, setSettingsPanelOpen, + setHelpDialogBoxOpen } = useAppState(); const [isRepoDropdownOpen, setIsRepoDropdownOpen] = useState(false); const repoDropdownRef = useRef(null); @@ -266,10 +267,13 @@ export const Header = ({ onFocusNode, availableRepos = [], onSwitchRepo }: Heade className="w-9 h-9 flex items-center justify-center rounded-md text-text-secondary hover:bg-hover hover:text-text-primary transition-colors" title="AI Settings" > - + - {/* AI Button */} diff --git a/gitnexus-web/src/components/HelpPanel.tsx b/gitnexus-web/src/components/HelpPanel.tsx new file mode 100644 index 0000000000..8952d65a85 --- /dev/null +++ b/gitnexus-web/src/components/HelpPanel.tsx @@ -0,0 +1,390 @@ +import React, { useState } from 'react'; +import { X, GitBranch, Search, Filter, Zap, Keyboard, BarChart2, HelpCircle } from 'lucide-react'; + +interface HelpPanelProps { + isOpen: boolean; + onClose: () => void; + nodeCount: number; + edgeCount: number; +} + +type TabId = 'overview' | 'graph' | 'search' | 'ai' | 'shortcuts' | 'status'; + +interface Tab { + id: TabId; + label: string; + icon: React.ReactNode; +} + +const tabs: Tab[] = [ + { id: 'overview', label: 'Overview', icon: }, + { id: 'graph', label: 'Graph & nodes', icon: }, + { id: 'search', label: 'Search & filter', icon: }, + { id: 'ai', label: 'Nexus AI', icon: }, + { id: 'shortcuts', label: 'Shortcuts', icon: }, + { id: 'status', label: 'Status bar', icon: }, +]; + +const shortcuts = [ + { label: 'Search nodes', mac: '⌘ K', win: 'Ctrl K' }, + { label: 'Deselect / close', mac: 'Esc', win: 'Esc' }, +]; + +const nodeColors = [ + { color: '#10b981', label: 'Function', desc: 'Function declarations' }, + { color: '#3b82f6', label: 'File', desc: 'Source files' }, + { color: '#f59e0b', label: 'Class', desc: 'Class declarations' }, + { color: '#14b8a6', label: 'Method', desc: 'Class methods' }, + { color: '#ec4899', label: 'Interface', desc: 'TypeScript interfaces' }, + { color: '#6366f1', label: 'Folder', desc: 'Directory nodes' }, +]; + +const getStatusItems = (nodeCount: number, edgeCount: number) => [ + { badge: , title: 'Ready', desc: 'Graph is fully loaded and interactive' }, + { badge: {nodeCount}, title: 'Nodes count', desc: 'Total files and symbols in the graph' }, + { badge: {edgeCount}, title: 'Edges count', desc: 'Import / dependency connections' }, + { badge: Semantic Ready, title: 'AI index status', desc: 'Repo is fully indexed for AI queries' }, + // { badge: typescript, title: 'Language', desc: 'Primary language detected in the repo' }, +]; + +const kbdStyle: React.CSSProperties = { + fontSize: 11, + background: 'rgba(255,255,255,0.08)', + borderRadius: 4, + padding: '2px 8px', + color: '#e2e2e8', + fontFamily: 'monospace', + border: '0.5px solid rgba(255,255,255,0.12)', + whiteSpace: 'nowrap', +}; + +const kbdWinStyle: React.CSSProperties = { + ...kbdStyle, + color: '#93c5fd', +}; + +function TabContent({ active, nodeCount, edgeCount }: { + active: TabId; + nodeCount: number; + edgeCount: number; +}) { + if (active === 'overview') return ( +
+

Getting started

+ +
+

What is GitNexus?

+

An interactive graph explorer for your codebase. Every file, function, and import becomes a node you can explore, query, and navigate visually.

+
+ +
+

Your current repo

+

+ Loaded: {nodeCount} nodes · {edgeCount} edges +

+
+ +
+

Three ways to explore

+

+ 1. Click nodes to inspect +
+ 2. Search by name or type +
+ 3. Ask Nexus AI a natural language question +

+
+ +
+

Navigation

+

+ · Scroll to zoom
+ · Click and drag to pan
+ · Double-click a node to focus its subgraph +

+
+
+ ); + + if (active === 'graph') return ( +
+

Node color legend

+ + {nodeColors.map(({ color, label, desc }) => ( +
+ +
+

{label} nodes

+

{desc}

+
+
+ ))} + +
+ +

+ Node size reflects connection count — larger nodes are depended on by more files. Edges point from importer → imported. +

+ +
+

+ Click any node to open its detail panel — showing imports, exports, and reverse dependencies. +

+
+
+ ); + + if (active === 'search') return ( +
+

Search & filter

+ +
+
+ ⌘K/ + Ctrl K +

Search nodes

+
+

+ Search by filename, function name, or import path. Matching nodes are highlighted live in the graph. +

+
+ +
+
+ +

Filter panel

+
+

+ Use the filter icon in the left sidebar to isolate specific node types, hide leaf nodes, or focus on a depth range from a selected root. +

+
+ +
+

Search syntax

+ {[ + { query: 'auth', hint: 'match by name fragment' }, + { query: './utils/', hint: 'match by path prefix' }, + { query: 'type:config', hint: 'filter by node type' }, + ].map(({ query, hint }) => ( +
+ {query} + {hint} +
+ ))} +
+
+ ); + + if (active === 'ai') return ( +
+

Nexus AI

+ +
+

✓ Semantic Ready

+

+ Your repo is indexed and ready for semantic queries. Nexus AI understands code structure and relationships, not just file names. +

+
+ +

Try asking:

+ {[ + '"Which files depend on the auth module?"', + '"Find circular dependencies in this repo"', + '"What are the most connected components?"', + '"Show me all files that import useEffect"', + ].map(q => ( +
{q}
+ ))} + +
+ +

+ Open the prompt via the{' '} + Nexus AI button (top-right). +

+
+ ); + + if (active === 'shortcuts') return ( +
+ {/* Column headers */} +
+ Action + Mac + Windows +
+ + {shortcuts.map(({ label, mac, win }, i) => ( +
+ {label} + + {mac} + + + {win} + +
+ ))} +
+ ); + + if (active === 'status') return ( +
+

Status bar explained

+ {getStatusItems(nodeCount, edgeCount).map(({ badge, title, desc }) => ( +
+ {badge} +
+

{title}

+

{desc}

+
+
+ ))} +
+ ); + + return null; +} + +export const HelpPanel = ({ isOpen, onClose, nodeCount, edgeCount }: HelpPanelProps) => { + const [active, setActive] = useState('overview'); + + if (!isOpen) return null; + + return ( +
+ {/* Backdrop */} +
+ + {/* Panel */} +
+ + {/* Header */} +
+
+
+ +
+
+

Help & Reference

+

GitNexus — graph explorer

+
+
+ +
+ + {/* Body: sidebar + content */} +
+ + {/* Sidebar nav */} +
+ {tabs.map(({ id, label, icon }) => { + const isActive = active === id; + return ( + + ); + })} +
+ + {/* Content pane */} +
+ +
+
+ + {/* Footer */} +
+ GitNexus — open source codebase graph explorer + + Docs & GitHub ↗ + +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/gitnexus-web/src/hooks/useAppState.tsx b/gitnexus-web/src/hooks/useAppState.tsx index 08dc15f2cf..5c6547e8e0 100644 --- a/gitnexus-web/src/hooks/useAppState.tsx +++ b/gitnexus-web/src/hooks/useAppState.tsx @@ -145,7 +145,9 @@ interface AppState { llmSettings: LLMSettings; updateLLMSettings: (updates: Partial) => void; isSettingsPanelOpen: boolean; + isHelpDialogBoxOpen: boolean; setSettingsPanelOpen: (open: boolean) => void; + setHelpDialogBoxOpen: (open: boolean) => void; isAgentReady: boolean; isAgentInitializing: boolean; agentError: string | null; @@ -292,6 +294,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { // LLM/Agent state const [llmSettings, setLLMSettings] = useState(loadSettings); const [isSettingsPanelOpen, setSettingsPanelOpen] = useState(false); + const [isHelpDialogBoxOpen, setHelpDialogBoxOpen] = useState(false); const [isAgentReady, setIsAgentReady] = useState(false); const [isAgentInitializing, setIsAgentInitializing] = useState(false); const [agentError, setAgentError] = useState(null); @@ -1190,6 +1193,8 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { updateLLMSettings, isSettingsPanelOpen, setSettingsPanelOpen, + isHelpDialogBoxOpen, + setHelpDialogBoxOpen, isAgentReady, isAgentInitializing, agentError,