Thank you for your interest in contributing to Local Lens! We're excited to have you join our community of developers working to make photo organization more intelligent, private, and accessible.
Local Lens is committed to providing a privacy-first, AI-powered photo organization solution that works entirely offline. Every contribution should align with our core principles:
- Privacy First: All processing happens locally - no data leaves the user's device
- Cross-Platform: Support for Windows, macOS, and Linux
- User-Friendly: Intuitive interface accessible to non-technical users
- Performance: Efficient handling of large photo collections
- Open Source: Transparent, community-driven development
Before contributing, ensure you have the development environment set up:
- Clone the repository:
git clone https://github.com/ashesbloom/LocalLens.git
cd LocalLens-
Install development dependencies (see README.md for detailed setup instructions):
- Node.js (v18+)
- Rust (latest stable)
- Python (3.11+)
- Platform-specific build tools (CMake, Visual Studio Build Tools, etc.)
-
Set up the development environment:
Windows:
# Backend setup
cd backend
python -m venv venv
.\venv\Scripts\activate
pip install -r requirements.txt
# Frontend setup
cd ../frontend
pnpm installmacOS (Intel & Apple Silicon):
# Install required system dependencies first
brew install cmake dlib imagemagick
# Backend setup
cd backend
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
# Frontend setup
cd ../frontend
pnpm installLinux:
# Install system dependencies (Ubuntu/Debian)
sudo apt install build-essential cmake libopenblas-dev liblapack-dev imagemagick
# Backend setup
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Frontend setup
cd ../frontend
pnpm install- Run the application in development mode:
# Terminal 1: Start backend
cd backend
python main.py
# Terminal 2: Start frontend
cd frontend
pnpm run tauri devWe welcome various types of contributions:
- 🐛 Bug Fixes: Help us identify and resolve issues
- ✨ New Features: Implement new functionality
- 📚 Documentation: Improve guides, README, or code comments
- 🎨 UI/UX Improvements: Enhance the user interface and experience
- ⚡ Performance Optimizations: Improve speed and efficiency
- 🧪 Testing: Add or improve test coverage
- 🌍 Localization: Add support for new languages
- 🔧 Tooling: Improve build processes or development workflows
- Check existing issues: Browse GitHub Issues to see if your idea is already being discussed
- Create an issue: For new features or significant changes, create an issue first to discuss the approach
- Join the discussion: Comment on existing issues if you'd like to work on them
# Fork the repository on GitHub, then:
git clone https://github.com/YOUR_USERNAME/LocalLens.git
cd LocalLens
git remote add upstream https://github.com/ashesbloom/LocalLens.git
# Create a feature branch
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix- Follow our coding standards
- Write clear, descriptive commit messages
- Test your changes thoroughly
- Update documentation if needed
# Run backend tests
cd backend
python -m pytest tests/
# Run frontend tests
cd frontend
pnpm test
# Test the full application
pnpm run tauri devgit add .
git commit -m "feat: add new sorting algorithm for date-based organization"
git push origin feature/your-feature-name- Go to the LocalLens repository
- Click "New Pull Request"
- Select your branch and provide a clear description
- Link any related issues
- Follow PEP 8
- Use Black for code formatting
- Use flake8 for linting
- Maximum line length: 88 characters
from typing import List, Optional
import logging
logger = logging.getLogger(__name__)
class PhotoOrganizer:
"""Handles intelligent photo organization with AI-powered categorization."""
def __init__(self, config: dict) -> None:
self.config = config
self._initialize_models()
def organize_photos(self, photo_paths: List[str]) -> Optional[dict]:
"""
Organize photos using configured sorting criteria.
Args:
photo_paths: List of absolute paths to photo files
Returns:
Dictionary containing organization results or None if failed
"""
try:
# Implementation here
pass
except Exception as e:
logger.error(f"Failed to organize photos: {e}")
return None- Add new dependencies to
requirements.txt - Pin versions for stability
- Use virtual environments for testing
- Use ESLint and Prettier
- Follow Airbnb JavaScript Style Guide
- Use functional components with hooks
- Use descriptive variable and function names
import React, { useState, useEffect } from 'react';
import './PhotoGrid.css';
const PhotoGrid = ({ photos, onPhotoSelect }) => {
const [selectedPhotos, setSelectedPhotos] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
// Load photos when component mounts
loadPhotos();
}, []);
const handlePhotoClick = (photo) => {
setSelectedPhotos(prev => [...prev, photo]);
onPhotoSelect?.(photo);
};
return (
<div className="photo-grid">
{photos.map(photo => (
<PhotoCard
key={photo.id}
photo={photo}
onClick={() => handlePhotoClick(photo)}
isSelected={selectedPhotos.includes(photo)}
/>
))}
</div>
);
};
export default PhotoGrid;- Follow Rust Style Guide
- Use
cargo fmtfor formatting - Use
cargo clippyfor linting - Write comprehensive documentation
use tauri::command;
use std::path::PathBuf;
#[derive(Debug)]
pub struct AppError {
message: String,
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
/// Validates that the selected path exists and is accessible
#[command]
pub async fn validate_photo_path(path: String) -> Result<bool, AppError> {
let photo_path = PathBuf::from(&path);
if !photo_path.exists() {
return Err(AppError {
message: format!("Path does not exist: {}", path),
});
}
Ok(photo_path.is_dir() || is_supported_image_format(&photo_path))
}- Write unit tests for all new functions
- Use
pytestfor testing framework - Mock external dependencies
- Test error handling and edge cases
import pytest
from unittest.mock import Mock, patch
from backend.organizer_logic import PhotoOrganizer
def test_photo_organizer_initialization():
"""Test that PhotoOrganizer initializes correctly."""
config = {"sorting_mode": "date", "face_recognition": True}
organizer = PhotoOrganizer(config)
assert organizer.config == config
@patch('backend.organizer_logic.face_recognition')
def test_face_detection_error_handling(mock_face_recognition):
"""Test that face detection errors are handled gracefully."""
mock_face_recognition.side_effect = Exception("Model failed to load")
organizer = PhotoOrganizer({"face_recognition": True})
result = organizer.detect_faces("test_image.jpg")
assert result is None # Should return None on error- Write unit tests for components
- Use React Testing Library
- Test user interactions
- Mock API calls
import { render, screen, fireEvent } from '@testing-library/react';
import PhotoGrid from '../components/PhotoGrid';
test('renders photo grid with photos', () => {
const mockPhotos = [
{ id: 1, path: '/path/to/photo1.jpg', name: 'photo1.jpg' },
{ id: 2, path: '/path/to/photo2.jpg', name: 'photo2.jpg' }
];
render(<PhotoGrid photos={mockPhotos} />);
expect(screen.getByText('photo1.jpg')).toBeInTheDocument();
expect(screen.getByText('photo2.jpg')).toBeInTheDocument();
});
test('calls onPhotoSelect when photo is clicked', () => {
const mockOnPhotoSelect = jest.fn();
const mockPhotos = [{ id: 1, path: '/path/to/photo1.jpg', name: 'photo1.jpg' }];
render(<PhotoGrid photos={mockPhotos} onPhotoSelect={mockOnPhotoSelect} />);
fireEvent.click(screen.getByText('photo1.jpg'));
expect(mockOnPhotoSelect).toHaveBeenCalledWith(mockPhotos[0]);
});- Write clear docstrings for all functions and classes
- Include parameter types and return values
- Provide usage examples for complex functions
- Document any side effects or assumptions
Follow the Conventional Commits specification:
type(scope): description
feat(backend): add support for HEIC image format
fix(ui): resolve photo grid layout issues on mobile
docs(readme): update installation instructions
test(organizer): add unit tests for face recognition
refactor(api): improve error handling in photo endpoints
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringtest: Adding or updating testschore: Maintenance tasks
- Profile code with large photo collections (10,000+ images)
- Use async/await for I/O operations
- Implement progress tracking for long operations
- Consider memory usage when processing multiple images
- Use appropriate data structures (sets for deduplication, etc.)
- Lazy load images in photo grids
- Implement virtual scrolling for large lists
- Debounce search inputs
- Use React.memo for expensive components
- Optimize re-renders with useCallback and useMemo
- Keep bundle sizes reasonable
- Use code splitting where appropriate
- Optimize images and assets
- Monitor build times and dependencies
# ✅ Good - Cross-platform path handling
import os
from pathlib import Path
photo_dir = Path(user_input) / "photos"
config_file = Path.home() / ".locallens" / "config.json"
# ❌ Bad - Platform-specific paths
photo_dir = "C:\\Users\\Photos" # Windows only
config_file = "/home/user/.locallens/config.json" # Unix only- Test process spawning on all platforms
- Handle platform-specific executable extensions
- Consider different shell environments
- Test keyboard shortcuts on all platforms
- Consider platform-specific UI conventions
- Handle different screen densities and sizes
- Never log sensitive user data (file paths, personal info)
- Ensure all processing happens locally
- Don't send telemetry or analytics data
- Be transparent about any network requests
# ✅ Good - Validate and sanitize inputs
def process_photo_path(user_path: str) -> Optional[Path]:
try:
path = Path(user_path).resolve()
if not path.exists() or not path.is_file():
return None
if path.suffix.lower() not in SUPPORTED_FORMATS:
return None
return path
except (OSError, ValueError):
return None
# ❌ Bad - Direct usage without validation
def process_photo_path(user_path: str) -> Path:
return Path(user_path) # Could be exploited- Code follows our style guidelines
- All tests pass locally
- New features have appropriate tests
- Documentation is updated
- Commit messages follow conventional format
- Changes are backwards compatible (or breaking changes are documented)
## Description
Brief description of what this PR does.
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed on [Windows/macOS/Linux]
## Screenshots (if applicable)
Add screenshots to show UI changes.
## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] Any dependent changes have been merged and publishedWe appreciate all contributions! Contributors will be:
- Listed in our README acknowledgments
- Mentioned in release notes for significant contributions
- Invited to join our contributors' Discord channel (coming soon)
- Discussion: Start a GitHub Discussion
- Issues: Report bugs via GitHub Issues
- Email: Contact maintainers at mayankpandeydk123@gmail.com
By contributing to Local Lens, you agree that your contributions will be licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
Thank you for helping make Local Lens better! 🚀
Together, we're building the future of privacy-first photo organization.