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.
This commit is contained in:
Berkcan Gümüşışık
2026-03-23 12:04:02 +03:00
parent 25ebf6d950
commit b0cb17793a
5 changed files with 588 additions and 1 deletions
+270
View File
@@ -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
<a href="README.md">English</a> | <a href="README.fr-FR.md">Français</a>
```
## 🐛 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.** 🚀
@@ -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<string | null>(null);
const [showKeyboardHelp, setShowKeyboardHelp] = useState(false);
// Define keyboard shortcuts
const shortcuts = useMemo<KeyboardShortcut[]>(
() => [
// 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<HTMLInputElement>(
'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() {
<div className="flex items-center gap-4">
<DiffToggle />
<LayerLegend />
<button
onClick={() => setShowKeyboardHelp(true)}
className="text-text-muted hover:text-gold transition-colors"
title="Keyboard shortcuts (Shift + ?)"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</button>
</div>
</header>
@@ -135,6 +256,14 @@ function App() {
</div>
)}
</div>
{/* Keyboard shortcuts help modal */}
{showKeyboardHelp && (
<KeyboardShortcutsHelp
shortcuts={shortcuts}
onClose={() => setShowKeyboardHelp(false)}
/>
)}
</div>
);
}
@@ -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<string, KeyboardShortcut[]>);
return (
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50"
onClick={onClose}
>
<div
className="glass rounded-lg shadow-2xl max-w-2xl w-full max-h-[80vh] overflow-auto m-4"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="sticky top-0 glass-heavy border-b border-border-subtle px-6 py-4 flex items-center justify-between">
<div>
<h2 className="text-xl font-serif text-text-primary">
Keyboard Shortcuts
</h2>
<p className="text-xs text-text-muted mt-1">
Press <kbd className="kbd">?</kbd> anytime to toggle this help
</p>
</div>
<button
onClick={onClose}
className="text-text-muted hover:text-text-primary transition-colors"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
{/* Shortcuts list */}
<div className="p-6 space-y-6">
{Object.entries(groupedShortcuts).map(([category, categoryShortcuts]) => (
<div key={category}>
<h3 className="text-sm font-semibold text-gold uppercase tracking-wider mb-3">
{category}
</h3>
<div className="space-y-2">
{categoryShortcuts.map((shortcut, index) => (
<div
key={index}
className="flex items-center justify-between py-2 px-3 rounded hover:bg-elevated transition-colors"
>
<span className="text-sm text-text-secondary">
{shortcut.description}
</span>
<kbd className="kbd">{formatShortcutKey(shortcut)}</kbd>
</div>
))}
</div>
</div>
))}
</div>
{/* Footer */}
<div className="sticky bottom-0 glass-heavy border-t border-border-subtle px-6 py-3 text-center">
<p className="text-xs text-text-muted">
Press <kbd className="kbd">ESC</kbd> to close
</p>
</div>
</div>
</div>
);
}
@@ -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(" + ");
}
@@ -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 {