Thank you for your interest in contributing to Better Shot! This document provides guidelines and instructions for contributing to the project.
- Code of Conduct
- Getting Started
- Development Setup
- Project Structure
- Coding Standards
- Testing
- Pull Request Process
- Technology Stack
Be respectful, constructive, and professional in all interactions. Focus on the code and ideas, not the person.
-
Fork the repository and clone your fork:
git clone https://github.com/YOUR_USERNAME/better-shot.git cd better-shot -
Install dependencies:
pnpm install
-
Set up development environment:
- Ensure you have Rust installed (for Tauri backend)
- Ensure you have Node.js 18+ and pnpm installed
- On macOS, grant Screen Recording permission when prompted
If you want to test the production build, you can install Better Shot via Homebrew:
brew install --cask bettershotRequirements: macOS >= 10.15
Alternatively, download the latest release from GitHub Releases.
- Node.js: 18 or higher
- pnpm: Latest version
- Rust: Latest stable version (for Tauri)
- macOS: Required for development (app is macOS-specific)
Start the development server:
pnpm tauri devThis will:
- Start the Vite dev server for the frontend
- Compile the Rust backend
- Launch the Tauri application window with hot-reload
pnpm tauri buildThe installer will be located in src-tauri/target/release/bundle/
Run TypeScript type checking:
pnpm lint:ciRun Rust tests:
pnpm test:rustbettershot/
├── src/ # Frontend React application
│ ├── components/ # React components
│ │ ├── editor/ # Image editor components
│ │ │ ├── AnnotationCanvas.tsx # Canvas for drawing annotations
│ │ │ ├── AnnotationToolbar.tsx # Toolbar for annotation tools
│ │ │ ├── PropertiesPanel.tsx # Panel for editing annotation properties
│ │ │ ├── BackgroundSelector.tsx # Background selection UI
│ │ │ ├── AssetGrid.tsx # Asset library grid
│ │ │ ├── EffectsPanel.tsx # Blur and noise controls
│ │ │ └── ImageRoundnessControl.tsx # Border radius control
│ │ │ ├── overlay/ # Quick overlay components
│ │ │ │ └── QuickOverlay.tsx # Floating preview overlay with auto-fade
│ │ ├── preferences/ # Settings and preferences
│ │ │ ├── PreferencesPage.tsx # Main preferences page
│ │ │ ├── BackgroundImageSelector.tsx # Default background picker
│ │ │ └── KeyboardShortcutManager.tsx # Shortcut configuration
│ │ ├── onboarding/ # First-run onboarding flow
│ │ │ ├── OnboardingFlow.tsx # Multi-step onboarding
│ │ │ ├── OnboardingStep.tsx # Individual step component
│ │ │ └── OnboardingProgress.tsx # Progress indicator
│ │ ├── landing/ # Landing page components
│ │ └── ui/ # Reusable UI primitives
│ ├── hooks/ # Custom React hooks
│ │ ├── useEditorSettings.ts # Editor state management
│ │ └── usePreviewGenerator.ts # Canvas preview generation
│ ├── lib/ # Utility functions
│ │ ├── annotation-utils.ts # Annotation rendering utilities
│ │ ├── auto-process.ts # Auto-apply background logic
│ │ ├── canvas-utils.ts # Canvas manipulation utilities
│ │ ├── onboarding.ts # Onboarding state management
│ │ └── utils.ts # General utilities
│ ├── types/ # TypeScript type definitions
│ │ └── annotations.ts # Annotation type definitions
│ └── assets/ # Static assets (images, etc.)
├── public/ # Static files copied to dist
├── src-tauri/ # Rust backend (Tauri)
│ ├── src/
│ │ ├── commands.rs # Tauri command handlers
│ │ ├── clipboard.rs # Clipboard operations (native macOS)
│ │ ├── image.rs # Image processing
│ │ ├── ocr.rs # OCR text recognition (macOS Vision framework)
│ │ ├── screenshot.rs # Screenshot capture
│ │ ├── utils.rs # Utility functions
│ │ └── lib.rs # Application entry point
│ └── Cargo.toml # Rust dependencies
├── AGENTS.md # UI/UX design guidelines
└── package.json # Node.js dependencies and scripts
- Search First: Before implementing, search the codebase for similar functionality
- Reuse First: Extend existing patterns and components before creating new ones
- No Assumptions: Only use information from files, user messages, or tool results
- SOLID Principles: Keep code simple but maintain separation of concerns
- File Size: Aim to keep files under 300 lines - split when it improves clarity
- No Code Comments: Write self-explanatory code instead
- Alphabetical Imports: Keep imports sorted alphabetically
- Strict TypeScript: All code must pass
pnpm lint:ci(TypeScript strict mode) - Type Safety: Use proper TypeScript types, avoid
any - Component Structure:
- Use functional components with hooks
- Keep components focused and single-purpose
- Extract reusable logic into custom hooks
- Error Handling: Use
Result<T, String>for error handling - Documentation: Use
//!for module-level docs,///for function docs - Module Organization: Keep modules focused and well-organized
- Async/Await: Use async/await for Tauri commands
- Resource Management: Properly handle file paths and temporary files
Refer to AGENTS.md for detailed UI/UX constraints. Key points:
- MUST use Tailwind CSS defaults (spacing, radius, shadows) before custom values
- MUST use
motion/react(formerlyframer-motion) when JavaScript animation is required - MUST use
cnutility (clsx+tailwind-merge) for class logic
- MUST use accessible component primitives for keyboard/focus behavior (
Base UI,React Aria,Radix) - MUST use the project's existing component primitives first
- MUST add
aria-labelto icon-only buttons - NEVER rebuild keyboard or focus behavior by hand
- MUST use
AlertDialogfor destructive or irreversible actions - SHOULD use structural skeletons for loading states
- NEVER use
h-screen, useh-dvh - MUST respect
safe-area-insetfor fixed elements - MUST show errors next to where the action happens
- NEVER add animation unless explicitly requested
- MUST animate only compositor props (
transform,opacity) - NEVER animate layout properties (
width,height,top,left,margin,padding) - NEVER exceed
200msfor interaction feedback - MUST respect
prefers-reduced-motion
- MUST use
text-balancefor headings andtext-prettyfor body/paragraphs - MUST use
tabular-numsfor data - SHOULD use
truncateorline-clampfor dense UI
- NEVER use gradients unless explicitly requested
- NEVER use purple or multicolor gradients
- NEVER use glow effects as primary affordances
- SHOULD use Tailwind CSS default shadow scale
- MUST give empty states one clear next action
// Good: Self-explanatory code, alphabetical imports
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { useState } from "react";
export function MyComponent() {
const [value, setValue] = useState("");
return (
<Card>
<CardContent>
<Button onClick={() => setValue("test")}>Click</Button>
</CardContent>
</Card>
);
}// Good: Clear function names, proper error handling
pub async fn my_command(param: String) -> Result<String, String> {
let result = process_data(¶m)?;
Ok(result)
}Write tests for critical paths only:
- Core screenshot capture functionality
- Image processing operations (blur, shadow, background effects)
- OCR text recognition (macOS Vision framework integration)
- Clipboard operations
- Annotation rendering and manipulation
- Error handling in critical flows
Use AAA pattern (Arrange, Act, Assert) with comments:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_filename() {
// Arrange
let prefix = "screenshot";
let extension = "png";
// Act
let filename = generate_filename(prefix, extension).unwrap();
// Assert
assert!(filename.starts_with(prefix));
assert!(filename.ends_with(extension));
}
}pnpm test:rust- Search the codebase for similar functionality
- Reuse existing patterns and components
- Run linting:
pnpm lint:ci - Run tests:
pnpm test:rust - Test manually: Ensure the feature works in development mode
- Clear Title: Use descriptive, concise titles
- Description: Explain what and why, not how (code shows how)
- Small PRs: Prefer smaller, focused PRs over large ones
- One Feature: One feature or fix per PR
- Branch Naming: Use descriptive branch names (e.g.,
fix/clipboard-error,feat/custom-shortcuts)
When creating a PR, include:
- What: Brief description of changes
- Why: Reason for the change
- Testing: How you tested the changes
- Screenshots: If applicable (UI changes)
- All PRs require review before merging
- Address review comments promptly
- Keep discussions focused on code and ideas
- Be open to feedback and suggestions
- React 19: UI framework
- TypeScript 5.8: Type safety
- Vite 7: Build tool and dev server
- Tailwind CSS 4: Styling
- Sonner: Toast notifications
- Rust: System programming language
- Tauri 2: Desktop app framework
- xcap: Screenshot capture library
- image: Image processing
- objc2-vision: macOS Vision framework bindings for OCR text recognition
- @tauri-apps/plugin-store: Settings persistence
- @tauri-apps/plugin-global-shortcut: Global hotkeys
- pnpm: Package manager
- TypeScript: Type checking
- Cargo: Rust package manager
- Search for similar functionality
- Check existing components/utilities
- Create feature branch:
git checkout -b feat/feature-name - Implement following coding standards
- Test thoroughly
- Run linting and tests
- Submit PR
- Reproduce the bug
- Search codebase for related code
- Create fix branch:
git checkout -b fix/bug-description - Implement fix
- Add test if it's a critical path
- Run linting and tests
- Submit PR with clear description
- Check
src/components/ui/for existing primitives - Use existing primitives when possible
- Follow UI guidelines from
AGENTS.md - Ensure accessibility (keyboard navigation, ARIA labels)
- Use
cnutility for class merging - Test with different screen sizes
- Add the annotation type to
src/types/annotations.ts - Update
AnnotationCanvas.tsxto handle creation and rendering - Add tool icon to
AnnotationToolbar.tsx - Add property controls to
PropertiesPanel.tsxif needed - Update
annotation-utils.tsfor rendering logic - Test drawing, selection, movement, and deletion
- Add the capture mode type to
CaptureModeinsrc/App.tsx - Add default shortcut configuration in
DEFAULT_SHORTCUTS(usually disabled by default) - Implement the Rust command handler in
src-tauri/src/commands.rs - Add the command to Tauri's command list in
src-tauri/src/lib.rs - Handle the capture mode in
handleCapturefunction insrc/App.tsx - Add UI button/trigger in the main app interface
- Update keyboard shortcut manager to support the new mode
- For OCR-like features, create a separate module (e.g.,
ocr.rs) if it requires platform-specific APIs
The Quick Overlay (src/components/overlay/QuickOverlay.tsx) is a floating window that shows capture previews:
- Auto-fade behavior: The overlay automatically fades out after 5 seconds and hides completely
- Window management: The overlay window is created in
src-tauri/src/lib.rsand shown viashowQuickOverlay()insrc/App.tsx - Event-driven: The overlay listens for
overlay-show-captureevents to display new captures - State persistence: Last capture path is stored in the settings store for persistence across app restarts
- Positioning: The overlay is positioned near the bottom-right of the monitor where the capture occurred
To modify the fade timing or behavior:
- Edit the
fadeDelayMsandfadeDurationMsconstants inQuickOverlay.tsx - The window is hidden using
getCurrentWindow().hide()after the fade completes
- Add the setting key to the store in
src/components/preferences/PreferencesPage.tsx - Update the
GeneralSettingsinterface if it's a general setting - Add UI controls in the appropriate preferences card
- Load the setting in
src/App.tsxduring initialization - Use
store.set()andstore.save()to persist changes - Call
onSettingsChange?.()to notify parent components
- Shortcuts are managed in
src/components/preferences/KeyboardShortcutManager.tsx - Default shortcuts are defined in
src/App.tsxasDEFAULT_SHORTCUTS - Shortcuts are registered using
@tauri-apps/plugin-global-shortcut - Changes trigger re-registration via
settingsVersionstate - OCR shortcut (
⌘⇧O) is available but disabled by default - users can enable it in Preferences
The homepage shows a comprehensive keyboard shortcuts reference organized into two sections:
- Capture Shortcuts - Dynamic shortcuts from user preferences (Region, Screen, Window, Cancel)
- Editor Shortcuts - Fixed editor shortcuts (Save, Copy, Undo, Redo, Delete annotation, Close editor)
To modify the shortcuts display:
- Edit the
Keyboard Shortcutscard insrc/App.tsx(around line 558) - Capture shortcuts use
getShortcutDisplay()to show user-configured values - Editor shortcuts are hardcoded with symbol representations (
⌘,⇧,⌥,⌫) - Use
<kbd>elements withtabular-numsclass for consistent display - Follow the existing pattern: label on the left,
<kbd>on the right
- Issues: Check existing issues before creating new ones
- Discussions: Use GitHub Discussions for questions
- Code Review: Be specific in review comments
By contributing, you agree that your contributions will be licensed under the same license as the project.
Thank you for contributing to Better Shot! 🎉