Skip to content

Latest commit

 

History

History
472 lines (353 loc) · 16 KB

File metadata and controls

472 lines (353 loc) · 16 KB

Contributing to Better Shot

Thank you for your interest in contributing to Better Shot! This document provides guidelines and instructions for contributing to the project.

Table of Contents

Code of Conduct

Be respectful, constructive, and professional in all interactions. Focus on the code and ideas, not the person.

Getting Started

  1. Fork the repository and clone your fork:

    git clone https://github.com/YOUR_USERNAME/better-shot.git
    cd better-shot
  2. Install dependencies:

    pnpm install
  3. 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

Installing Better Shot for Testing

If you want to test the production build, you can install Better Shot via Homebrew:

brew install --cask bettershot

Requirements: macOS >= 10.15

Alternatively, download the latest release from GitHub Releases.

Development Setup

Prerequisites

  • Node.js: 18 or higher
  • pnpm: Latest version
  • Rust: Latest stable version (for Tauri)
  • macOS: Required for development (app is macOS-specific)

Running the Application

Start the development server:

pnpm tauri dev

This will:

  • Start the Vite dev server for the frontend
  • Compile the Rust backend
  • Launch the Tauri application window with hot-reload

Building for Production

pnpm tauri build

The installer will be located in src-tauri/target/release/bundle/

Linting

Run TypeScript type checking:

pnpm lint:ci

Testing

Run Rust tests:

pnpm test:rust

Project Structure

bettershot/
├── 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

Coding Standards

General Principles

  1. Search First: Before implementing, search the codebase for similar functionality
  2. Reuse First: Extend existing patterns and components before creating new ones
  3. No Assumptions: Only use information from files, user messages, or tool results
  4. SOLID Principles: Keep code simple but maintain separation of concerns
  5. File Size: Aim to keep files under 300 lines - split when it improves clarity

TypeScript/React Guidelines

  • 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

Rust Guidelines

  • 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

UI/UX Guidelines

Refer to AGENTS.md for detailed UI/UX constraints. Key points:

Stack

  • MUST use Tailwind CSS defaults (spacing, radius, shadows) before custom values
  • MUST use motion/react (formerly framer-motion) when JavaScript animation is required
  • MUST use cn utility (clsx + tailwind-merge) for class logic

Components

  • 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-label to icon-only buttons
  • NEVER rebuild keyboard or focus behavior by hand

Interaction

  • MUST use AlertDialog for destructive or irreversible actions
  • SHOULD use structural skeletons for loading states
  • NEVER use h-screen, use h-dvh
  • MUST respect safe-area-inset for fixed elements
  • MUST show errors next to where the action happens

Animation

  • 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 200ms for interaction feedback
  • MUST respect prefers-reduced-motion

Typography

  • MUST use text-balance for headings and text-pretty for body/paragraphs
  • MUST use tabular-nums for data
  • SHOULD use truncate or line-clamp for dense UI

Design

  • 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

Code Style

TypeScript/React

// 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>
  );
}

Rust

// Good: Clear function names, proper error handling
pub async fn my_command(param: String) -> Result<String, String> {
    let result = process_data(&param)?;
    Ok(result)
}

Testing

When to Write Tests

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

Test Structure

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));
    }
}

Running Tests

pnpm test:rust

Pull Request Process

Before Submitting

  1. Search the codebase for similar functionality
  2. Reuse existing patterns and components
  3. Run linting: pnpm lint:ci
  4. Run tests: pnpm test:rust
  5. Test manually: Ensure the feature works in development mode

PR Guidelines

  1. Clear Title: Use descriptive, concise titles
  2. Description: Explain what and why, not how (code shows how)
  3. Small PRs: Prefer smaller, focused PRs over large ones
  4. One Feature: One feature or fix per PR
  5. Branch Naming: Use descriptive branch names (e.g., fix/clipboard-error, feat/custom-shortcuts)

PR Template

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)

Review Process

  • All PRs require review before merging
  • Address review comments promptly
  • Keep discussions focused on code and ideas
  • Be open to feedback and suggestions

Technology Stack

Frontend

  • React 19: UI framework
  • TypeScript 5.8: Type safety
  • Vite 7: Build tool and dev server
  • Tailwind CSS 4: Styling
  • Sonner: Toast notifications

Backend

  • 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

Plugins

  • @tauri-apps/plugin-store: Settings persistence
  • @tauri-apps/plugin-global-shortcut: Global hotkeys

Development Tools

  • pnpm: Package manager
  • TypeScript: Type checking
  • Cargo: Rust package manager

Common Tasks

Adding a New Feature

  1. Search for similar functionality
  2. Check existing components/utilities
  3. Create feature branch: git checkout -b feat/feature-name
  4. Implement following coding standards
  5. Test thoroughly
  6. Run linting and tests
  7. Submit PR

Fixing a Bug

  1. Reproduce the bug
  2. Search codebase for related code
  3. Create fix branch: git checkout -b fix/bug-description
  4. Implement fix
  5. Add test if it's a critical path
  6. Run linting and tests
  7. Submit PR with clear description

Adding a New UI Component

  1. Check src/components/ui/ for existing primitives
  2. Use existing primitives when possible
  3. Follow UI guidelines from AGENTS.md
  4. Ensure accessibility (keyboard navigation, ARIA labels)
  5. Use cn utility for class merging
  6. Test with different screen sizes

Adding a New Annotation Type

  1. Add the annotation type to src/types/annotations.ts
  2. Update AnnotationCanvas.tsx to handle creation and rendering
  3. Add tool icon to AnnotationToolbar.tsx
  4. Add property controls to PropertiesPanel.tsx if needed
  5. Update annotation-utils.ts for rendering logic
  6. Test drawing, selection, movement, and deletion

Adding a New Capture Mode

  1. Add the capture mode type to CaptureMode in src/App.tsx
  2. Add default shortcut configuration in DEFAULT_SHORTCUTS (usually disabled by default)
  3. Implement the Rust command handler in src-tauri/src/commands.rs
  4. Add the command to Tauri's command list in src-tauri/src/lib.rs
  5. Handle the capture mode in handleCapture function in src/App.tsx
  6. Add UI button/trigger in the main app interface
  7. Update keyboard shortcut manager to support the new mode
  8. For OCR-like features, create a separate module (e.g., ocr.rs) if it requires platform-specific APIs

Modifying Quick Overlay

The Quick Overlay (src/components/overlay/QuickOverlay.tsx) is a floating window that shows capture previews:

  1. Auto-fade behavior: The overlay automatically fades out after 5 seconds and hides completely
  2. Window management: The overlay window is created in src-tauri/src/lib.rs and shown via showQuickOverlay() in src/App.tsx
  3. Event-driven: The overlay listens for overlay-show-capture events to display new captures
  4. State persistence: Last capture path is stored in the settings store for persistence across app restarts
  5. 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 fadeDelayMs and fadeDurationMs constants in QuickOverlay.tsx
  • The window is hidden using getCurrentWindow().hide() after the fade completes

Adding a New Setting

  1. Add the setting key to the store in src/components/preferences/PreferencesPage.tsx
  2. Update the GeneralSettings interface if it's a general setting
  3. Add UI controls in the appropriate preferences card
  4. Load the setting in src/App.tsx during initialization
  5. Use store.set() and store.save() to persist changes
  6. Call onSettingsChange?.() to notify parent components

Modifying Keyboard Shortcuts

  1. Shortcuts are managed in src/components/preferences/KeyboardShortcutManager.tsx
  2. Default shortcuts are defined in src/App.tsx as DEFAULT_SHORTCUTS
  3. Shortcuts are registered using @tauri-apps/plugin-global-shortcut
  4. Changes trigger re-registration via settingsVersion state
  5. OCR shortcut (⌘⇧O) is available but disabled by default - users can enable it in Preferences

Updating the Homepage Keyboard Shortcuts Display

The homepage shows a comprehensive keyboard shortcuts reference organized into two sections:

  1. Capture Shortcuts - Dynamic shortcuts from user preferences (Region, Screen, Window, Cancel)
  2. Editor Shortcuts - Fixed editor shortcuts (Save, Copy, Undo, Redo, Delete annotation, Close editor)

To modify the shortcuts display:

  1. Edit the Keyboard Shortcuts card in src/App.tsx (around line 558)
  2. Capture shortcuts use getShortcutDisplay() to show user-configured values
  3. Editor shortcuts are hardcoded with symbol representations (, , , )
  4. Use <kbd> elements with tabular-nums class for consistent display
  5. Follow the existing pattern: label on the left, <kbd> on the right

Getting Help

  • Issues: Check existing issues before creating new ones
  • Discussions: Use GitHub Discussions for questions
  • Code Review: Be specific in review comments

License

By contributing, you agree that your contributions will be licensed under the same license as the project.


Thank you for contributing to Better Shot! 🎉