From b0cb17793af670d425c3bccc9516044eb0b84180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkcan=20G=C3=BCm=C3=BC=C5=9F=C4=B1=C5=9F=C4=B1k?= <75336900+berkcangumusisik@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:04:02 +0300 Subject: [PATCH] Add keyboard shortcuts UI and CONTRIBUTING.md Introduce keyboard shortcuts to the dashboard and add contributor docs. Adds a reusable useKeyboardShortcuts hook (with formatShortcutKey), a KeyboardShortcutsHelp modal component, and integrates shortcuts into App.tsx (registering keys like ?, Escape, /, ArrowLeft/Right, l, d and wiring store actions). Adds related styles (.glass-heavy, .kbd) to index.css. Also adds a comprehensive CONTRIBUTING.md with setup, workflow, testing, and PR guidelines. --- CONTRIBUTING.md | 270 ++++++++++++++++++ .../packages/dashboard/src/App.tsx | 131 ++++++++- .../src/components/KeyboardShortcutsHelp.tsx | 106 +++++++ .../src/hooks/useKeyboardShortcuts.ts | 57 ++++ .../packages/dashboard/src/index.css | 25 ++ 5 files changed, 588 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md create mode 100644 understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx create mode 100644 understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..64a8154 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,270 @@ +# Contributing to Understand Anything + +Thank you for your interest in contributing to Understand Anything! This document provides guidelines and instructions for contributing to the project. + +## 🌟 Ways to Contribute + +- **Bug Reports**: Found a bug? Open an issue with detailed reproduction steps +- **Feature Requests**: Have an idea? Share it in the issues section +- **Documentation**: Improve or translate documentation +- **Code**: Fix bugs, add features, or improve performance +- **Testing**: Write tests to improve code coverage + +## πŸš€ Getting Started + +### Prerequisites + +- Node.js >= 22 (developed on v24) +- pnpm >= 10 (pinned via `packageManager` field in root `package.json`) +- Git for version control + +### Setup + +1. **Fork and Clone** + ```bash + git clone https://github.com/YOUR_USERNAME/Understand-Anything.git + cd Understand-Anything + ``` + +2. **Install Dependencies** + ```bash + pnpm install + ``` + +3. **Build Core Package** + ```bash + pnpm --filter @understand-anything/core build + ``` + +4. **Run Tests** + ```bash + pnpm --filter @understand-anything/core test + pnpm --filter @understand-anything/skill test + ``` + +5. **Start Dashboard (Optional)** + ```bash + pnpm dev:dashboard + ``` + +## πŸ“ Development Workflow + +### 1. Create a Branch + +Create a descriptive branch name: +```bash +git checkout -b feat/my-feature # For new features +git checkout -b fix/bug-description # For bug fixes +git checkout -b docs/update-readme # For documentation +``` + +### 2. Make Changes + +- Write clean, readable code +- Follow existing code style and conventions +- Add tests for new functionality +- Update documentation as needed + +### 3. Test Your Changes + +```bash +# Run all tests +pnpm --filter @understand-anything/core test +pnpm --filter @understand-anything/skill test + +# Run linter +pnpm lint + +# Build packages +pnpm build +``` + +### 4. Commit Your Changes + +Write clear, descriptive commit messages: +```bash +git add . +git commit -m "feat: add keyboard shortcuts to dashboard" +``` + +**Commit Message Convention:** +- `feat:` - New feature +- `fix:` - Bug fix +- `docs:` - Documentation changes +- `style:` - Code style changes (formatting, etc.) +- `refactor:` - Code refactoring +- `test:` - Adding or updating tests +- `chore:` - Maintenance tasks + +### 5. Push and Create Pull Request + +```bash +git push origin your-branch-name +``` + +Then open a Pull Request on GitHub with: +- Clear title describing the change +- Detailed description of what changed and why +- Link to related issues (if any) +- Screenshots (for UI changes) + +## πŸ§ͺ Testing Guidelines + +### Writing Tests + +- Use Vitest for testing +- Place tests in `__tests__` directories or `*.test.ts` files +- Aim for high test coverage for new features +- Test edge cases and error conditions + +Example test structure: +```typescript +import { describe, it, expect } from 'vitest'; + +describe('MyFeature', () => { + it('should do something', () => { + // Arrange + const input = 'test'; + + // Act + const result = myFunction(input); + + // Assert + expect(result).toBe('expected'); + }); +}); +``` + +### Running Tests + +```bash +# Run all tests +pnpm test + +# Run tests for specific package +pnpm --filter @understand-anything/core test + +# Run tests in watch mode +pnpm --filter @understand-anything/core test --watch +``` + +## πŸ“š Code Style Guidelines + +### TypeScript + +- Use TypeScript strict mode +- Define explicit types for function parameters and return values +- Avoid `any` type - use `unknown` if type is truly unknown +- Use interfaces for object shapes +- Use type aliases for unions and complex types + +### Formatting + +- The project uses ESLint for code quality +- Consistent indentation (2 spaces) +- Use meaningful variable and function names +- Keep functions small and focused + +### React/Dashboard + +- Use functional components with hooks +- Keep components focused and single-purpose +- Use Zustand for state management +- Follow the existing component structure + +### File Organization + +``` +understand-anything-plugin/ +β”œβ”€β”€ packages/ +β”‚ β”œβ”€β”€ core/ # Core analysis engine +β”‚ β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ └── package.json +β”‚ └── dashboard/ # React dashboard +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ components/ +β”‚ β”‚ β”œβ”€β”€ utils/ +β”‚ β”‚ └── store.ts +β”‚ └── package.json +β”œβ”€β”€ src/ # Plugin skills implementation +β”œβ”€β”€ agents/ # AI agent prompts +└── skills/ # Skill definitions +``` + +## 🌍 Translation Guidelines + +### Adding a New Language + +1. Create `README.{language-code}.md` (e.g., `README.fr-FR.md`) +2. Translate all sections while maintaining formatting +3. Update main `README.md` to include language link +4. Keep technical terms in English where appropriate +5. Ensure all links still work + +Example: +```markdown +English | FranΓ§ais +``` + +## πŸ› Bug Reports + +When reporting bugs, include: + +- **Description**: Clear description of the issue +- **Steps to Reproduce**: Detailed steps to reproduce the bug +- **Expected Behavior**: What you expected to happen +- **Actual Behavior**: What actually happened +- **Environment**: OS, Node version, pnpm version +- **Screenshots**: If applicable +- **Error Messages**: Full error output + +## πŸ’‘ Feature Requests + +When requesting features: + +- **Use Case**: Describe the problem you're trying to solve +- **Proposed Solution**: How you envision the feature working +- **Alternatives**: Other solutions you've considered +- **Additional Context**: Any other relevant information + +## πŸ“‹ Pull Request Checklist + +Before submitting a PR, ensure: + +- [ ] Code follows the project's style guidelines +- [ ] All tests pass (`pnpm test`) +- [ ] New code has test coverage +- [ ] Documentation is updated (if needed) +- [ ] Commit messages follow convention +- [ ] PR description clearly explains changes +- [ ] No console.log or debug code left behind +- [ ] Branch is up to date with main + +## 🀝 Code Review Process + +1. **Automated Checks**: CI runs tests and linting +2. **Maintainer Review**: Project maintainers review the code +3. **Feedback**: Address any requested changes +4. **Approval**: Once approved, PR will be merged +5. **Cleanup**: Delete your branch after merge + +## πŸ“ž Getting Help + +- **Issues**: For bugs and feature requests +- **Discussions**: For questions and general discussion +- **Documentation**: Check existing docs first + +## πŸ“„ License + +By contributing, you agree that your contributions will be licensed under the MIT License. + +## πŸ™ Recognition + +Contributors will be recognized in: +- GitHub contributors list +- Release notes (for significant contributions) +- Special mentions for exceptional contributions + +--- + +**Thank you for contributing to Understand Anything! Your contributions help make code understanding accessible to everyone.** πŸš€ diff --git a/understand-anything-plugin/packages/dashboard/src/App.tsx b/understand-anything-plugin/packages/dashboard/src/App.tsx index 50df460..0768c72 100644 --- a/understand-anything-plugin/packages/dashboard/src/App.tsx +++ b/understand-anything-plugin/packages/dashboard/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useMemo } from "react"; import { validateGraph } from "@understand-anything/core/schema"; import { useDashboardStore } from "./store"; import GraphView from "./components/GraphView"; @@ -10,6 +10,9 @@ import DiffToggle from "./components/DiffToggle"; import LearnPanel from "./components/LearnPanel"; import PersonaSelector from "./components/PersonaSelector"; import ProjectOverview from "./components/ProjectOverview"; +import KeyboardShortcutsHelp from "./components/KeyboardShortcutsHelp"; +import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; +import type { KeyboardShortcut } from "./hooks/useKeyboardShortcuts"; function App() { const graph = useDashboardStore((s) => s.graph); @@ -20,7 +23,106 @@ function App() { const codeViewerOpen = useDashboardStore((s) => s.codeViewerOpen); const closeCodeViewer = useDashboardStore((s) => s.closeCodeViewer); const setDiffOverlay = useDashboardStore((s) => s.setDiffOverlay); + const selectNode = useDashboardStore((s) => s.selectNode); + const toggleLayers = useDashboardStore((s) => s.toggleLayers); + const toggleDiffMode = useDashboardStore((s) => s.toggleDiffMode); + const stopTour = useDashboardStore((s) => s.stopTour); + const nextTourStep = useDashboardStore((s) => s.nextTourStep); + const prevTourStep = useDashboardStore((s) => s.prevTourStep); const [loadError, setLoadError] = useState(null); + const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); + + // Define keyboard shortcuts + const shortcuts = useMemo( + () => [ + // Help + { + key: "?", + shiftKey: true, + description: "Show keyboard shortcuts", + action: () => setShowKeyboardHelp((prev) => !prev), + category: "General", + }, + // Navigation + { + key: "Escape", + description: "Close panels and modals", + action: () => { + if (showKeyboardHelp) { + setShowKeyboardHelp(false); + } else if (codeViewerOpen) { + closeCodeViewer(); + } else if (selectedNodeId) { + selectNode(null); + } else if (tourActive) { + stopTour(); + } + }, + category: "Navigation", + }, + { + key: "/", + description: "Focus search bar", + action: () => { + const searchInput = document.querySelector( + 'input[placeholder*="Search"]' + ); + searchInput?.focus(); + }, + category: "Navigation", + }, + // Tour controls + { + key: "ArrowRight", + description: "Next tour step", + action: () => { + if (tourActive) { + nextTourStep(); + } + }, + category: "Tour", + }, + { + key: "ArrowLeft", + description: "Previous tour step", + action: () => { + if (tourActive) { + prevTourStep(); + } + }, + category: "Tour", + }, + // View toggles + { + key: "l", + description: "Toggle layer visualization", + action: toggleLayers, + category: "View", + }, + { + key: "d", + description: "Toggle diff mode", + action: toggleDiffMode, + category: "View", + }, + ], + [ + showKeyboardHelp, + codeViewerOpen, + selectedNodeId, + tourActive, + closeCodeViewer, + selectNode, + stopTour, + nextTourStep, + prevTourStep, + toggleLayers, + toggleDiffMode, + ] + ); + + // Register keyboard shortcuts + useKeyboardShortcuts(shortcuts, !showKeyboardHelp); useEffect(() => { fetch("/knowledge-graph.json") @@ -91,6 +193,25 @@ function App() {
+
@@ -135,6 +256,14 @@ function App() { )} + + {/* Keyboard shortcuts help modal */} + {showKeyboardHelp && ( + setShowKeyboardHelp(false)} + /> + )} ); } diff --git a/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx b/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx new file mode 100644 index 0000000..7c4de2c --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx @@ -0,0 +1,106 @@ +import { useEffect } from "react"; +import type { KeyboardShortcut } from "../hooks/useKeyboardShortcuts"; +import { formatShortcutKey } from "../hooks/useKeyboardShortcuts"; + +interface KeyboardShortcutsHelpProps { + shortcuts: KeyboardShortcut[]; + onClose: () => void; +} + +export default function KeyboardShortcutsHelp({ + shortcuts, + onClose, +}: KeyboardShortcutsHelpProps) { + // Close on Escape key + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + } + }; + document.addEventListener("keydown", handleEscape); + return () => document.removeEventListener("keydown", handleEscape); + }, [onClose]); + + // Group shortcuts by category + const groupedShortcuts = shortcuts.reduce((acc, shortcut) => { + if (!acc[shortcut.category]) { + acc[shortcut.category] = []; + } + acc[shortcut.category].push(shortcut); + return acc; + }, {} as Record); + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+
+

+ Keyboard Shortcuts +

+

+ Press ? anytime to toggle this help +

+
+ +
+ + {/* Shortcuts list */} +
+ {Object.entries(groupedShortcuts).map(([category, categoryShortcuts]) => ( +
+

+ {category} +

+
+ {categoryShortcuts.map((shortcut, index) => ( +
+ + {shortcut.description} + + {formatShortcutKey(shortcut)} +
+ ))} +
+
+ ))} +
+ + {/* Footer */} +
+

+ Press ESC to close +

+
+
+
+ ); +} diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts b/understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts new file mode 100644 index 0000000..e49dda4 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts @@ -0,0 +1,57 @@ +import { useEffect } from "react"; + +export interface KeyboardShortcut { + key: string; + ctrlKey?: boolean; + shiftKey?: boolean; + altKey?: boolean; + metaKey?: boolean; + description: string; + action: () => void; + category: string; +} + +export function useKeyboardShortcuts( + shortcuts: KeyboardShortcut[], + enabled = true +) { + useEffect(() => { + if (!enabled) return; + + const handleKeyDown = (event: KeyboardEvent) => { + for (const shortcut of shortcuts) { + const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase(); + const ctrlMatches = shortcut.ctrlKey ? event.ctrlKey : !event.ctrlKey; + const shiftMatches = shortcut.shiftKey ? event.shiftKey : !event.shiftKey; + const altMatches = shortcut.altKey ? event.altKey : !event.altKey; + const metaMatches = shortcut.metaKey ? event.metaKey : !event.metaKey; + + if (keyMatches && ctrlMatches && shiftMatches && altMatches && metaMatches) { + // Prevent default for shortcuts that might conflict with browser + if (event.ctrlKey || event.metaKey || event.altKey) { + event.preventDefault(); + } + shortcut.action(); + break; + } + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [shortcuts, enabled]); +} + +export function formatShortcutKey(shortcut: KeyboardShortcut): string { + const keys: string[] = []; + + if (shortcut.ctrlKey || shortcut.metaKey) { + keys.push(navigator.platform.includes("Mac") ? "⌘" : "Ctrl"); + } + if (shortcut.shiftKey) keys.push("⇧"); + if (shortcut.altKey) keys.push(navigator.platform.includes("Mac") ? "βŒ₯" : "Alt"); + + keys.push(shortcut.key.toUpperCase()); + + return keys.join(" + "); +} diff --git a/understand-anything-plugin/packages/dashboard/src/index.css b/understand-anything-plugin/packages/dashboard/src/index.css index a767a27..a0f8ffa 100644 --- a/understand-anything-plugin/packages/dashboard/src/index.css +++ b/understand-anything-plugin/packages/dashboard/src/index.css @@ -71,6 +71,31 @@ body { -webkit-backdrop-filter: blur(12px); } +.glass-heavy { + background: rgba(20, 20, 20, 0.95); + border: 1px solid rgba(212, 165, 116, 0.15); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); +} + +/* Keyboard shortcut key styling */ +.kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.75rem; + height: 1.75rem; + padding: 0 0.5rem; + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 600; + color: var(--color-gold); + background: rgba(212, 165, 116, 0.1); + border: 1px solid rgba(212, 165, 116, 0.3); + border-radius: 0.25rem; + box-shadow: 0 1px 0 rgba(212, 165, 116, 0.2); +} + /* Animation keyframes */ @keyframes fadeSlideIn { from {