Thank you for your interest in contributing to Graphiti! This document provides guidelines and conventions for contributing to the project.
- Getting Started
- Development Setup
- Code Style Guide
- TypeScript Conventions
- React Patterns
- Testing Requirements
- Commit Messages
- Pull Request Process
- Fork the repository
- Clone your fork locally
- Create a feature branch from
main - Make your changes
- Submit a pull request
See docs/DEVELOPMENT.md for detailed setup instructions.
# Install dependencies
npm install
# Build the extension
npm run build
# Run tests
npm test
# Development mode with watch
npm run devCopy .env.example to .env and customize as needed:
cp .env.example .envSee docs/DEVELOPMENT.md for available environment variables.
- Open Chrome and navigate to
chrome://extensions - Enable "Developer mode"
- Click "Load unpacked"
- Select the
dist/folder
- Write clear, self-documenting code
- Prefer readability over cleverness
- Keep functions small and focused (< 50 lines)
- Use meaningful variable and function names
src/
├── background/ # Service worker (no DOM access)
├── content/ # Content scripts (page injection)
├── popup/ # Extension popup UI
├── sidepanel/ # Side panel feed UI
├── utils/ # Shared utilities
└── test/ # Test setup and mocks
| Type | Convention | Example |
|---|---|---|
| Files | kebab-case | nexus-client.ts |
| Components | PascalCase | PostCard.tsx |
| Functions | camelCase | generateUrlHashTag() |
| Constants | UPPER_SNAKE | MAX_RETRIES |
| Interfaces | PascalCase | NexusPost |
| Type aliases | PascalCase | LogLevel |
The project uses TypeScript strict mode. All code must pass type checking.
npm run build # Includes tsc type checking// ✅ Good: Explicit interface
interface UserProfile {
name: string;
bio?: string;
image?: string;
}
// ❌ Avoid: Inline object types for complex structures
function updateProfile(profile: { name: string; bio?: string }) { }
// ✅ Good: Use interface instead
function updateProfile(profile: UserProfile) { }// ❌ Avoid
function processData(data: any) { }
// ✅ Better: Use generics or unknown
function processData<T>(data: T) { }
function processData(data: unknown) { }
// ✅ Acceptable in test mocks only
const mockLogger: any = { debug: vi.fn() };// ✅ Good: async/await for readability
async function fetchPosts(): Promise<Post[]> {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
logger.error('API', 'Failed to fetch', error as Error);
throw error;
}
}
// ❌ Avoid: .then() chains
function fetchPosts(): Promise<Post[]> {
return fetch(url)
.then(r => r.json())
.catch(e => { throw e; });
}// ✅ Good: Functional component with TypeScript
interface PostCardProps {
post: NexusPost;
onSelect?: (id: string) => void;
}
function PostCard({ post, onSelect }: PostCardProps) {
return (
<div onClick={() => onSelect?.(post.id)}>
{post.content}
</div>
);
}
export default PostCard;// ✅ Good: Proper useEffect with cleanup
useEffect(() => {
const handler = (msg: Message) => { /* ... */ };
chrome.runtime.onMessage.addListener(handler);
return () => {
chrome.runtime.onMessage.removeListener(handler);
};
}, []); // Empty deps = mount/unmount only
// ✅ Good: Dependencies listed
useEffect(() => {
loadPosts();
}, [currentUrl, session]); // Re-run when these change// ✅ Good: Local state for component-specific data
const [loading, setLoading] = useState(false);
const [posts, setPosts] = useState<Post[]>([]);
// ✅ Good: Lift state for shared data
// Parent passes down via props or contextAll new code should include tests:
| Type | Location | Required For |
|---|---|---|
| Unit tests | __tests__/*.test.ts |
Utility functions |
| Integration tests | __tests__/*.integration.test.ts |
API clients |
| Component tests | __tests__/*.test.tsx |
React components |
import { describe, it, expect, vi, beforeEach } from 'vitest';
describe('generateUrlHashTag', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should generate deterministic hash for same URL', async () => {
const url = 'https://example.com';
const hash1 = await generateUrlHashTag(url);
const hash2 = await generateUrlHashTag(url);
expect(hash1).toBe(hash2);
expect(hash1).toHaveLength(10);
});
it('should handle errors gracefully', async () => {
await expect(generateUrlHashTag('')).resolves.toBeDefined();
});
});# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run specific test file
npm test -- crypto.test.ts
# Watch mode
npm test -- --watchFollow the Conventional Commits format:
<type>(<scope>): <description>
[optional body]
[optional footer]
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, no code change |
refactor |
Code change, no feature/fix |
test |
Adding/updating tests |
chore |
Build, deps, tooling |
feat(annotations): add highlight color picker
fix(auth): handle token expiry gracefully
docs(api): add usage examples to API reference
test(crypto): add edge case tests for UTF-16 encoding
refactor(content): extract DrawingManager to separate file- Tests pass:
npm test - Build succeeds:
npm run build - Code formatted: Follow style guide
- Documentation updated: If adding features
- CHANGELOG updated: For user-facing changes
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests added/updated
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guide
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] CHANGELOG updated- Submit PR against
mainbranch - Automated tests run via CI
- Maintainer reviews code
- Address feedback if needed
- Squash merge when approved
Open an issue for:
- Bug reports
- Feature requests
- General questions
Thank you for contributing! 🎉