Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,21 @@ Through [contributions, donations, and sponsorship](https://github.com/sponsors/

Contributions welcome! Check out our [Contribution Guide](docs/Contribute.md) and [documentation](https://github.com/LycheeOrg/Lychee/tree/master/docs) for setup, coding standards, and PR guidelines.

**AI-assisted contributions** are permitted — see our [AI/Claude Guidelines](docs/Contribute.md#using-aiclaude-for-contributions) and [AGENTS.md](AGENTS.md) for the Specification-Driven Development workflow.

## Installation

### Quick Try (Docker)

Want to quickly test Lychee? Use the minimal docker-compose template:

```bash
curl -O https://raw.githubusercontent.com/LycheeOrg/Lychee/master/docker-compose.minimal.yaml
docker compose -f docker-compose.minimal.yaml up -d
```

Then open http://localhost:8000 in your browser. This setup includes a separate worker container for background jobs.

### Docker (Recommended)

The easiest way to deploy Lychee with all dependencies configured:
Expand Down
64 changes: 63 additions & 1 deletion docs/Contribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,68 @@ git push origin feature/your-feature-name
- References to any related issues
- Screenshots (if applicable for UI changes)

## Using AI/Claude for Contributions

AI-assisted development is permitted and welcomed. However, contributions using AI tools must follow our **Specification-Driven Development (SDD)** workflow:

### Guidelines

1. **Read [AGENTS.md](../AGENTS.md) first** — This file contains the instructions that guide AI agents working on this codebase. It defines the workflow, guardrails, and expectations for AI-assisted development.

2. **Follow Spec-Driven Development** — AI-generated code must be anchored in explicit specifications:
- Start by creating or updating the feature specification at `docs/specs/4-architecture/features/<NNN>-<feature-name>/spec.md`
- Generate a feature plan (`plan.md`) and tasks checklist (`tasks.md`)
- Write tests before implementation (test-first cadence)
- Use the templates in `docs/specs/templates/` for consistency

3. **Understand before generating** — AI tools should explore and understand the existing codebase before proposing changes. Use the documentation structure in `docs/specs/` to build context.

4. **Quality gates still apply** — All AI-generated code must pass the same quality checks as human-written code:
- PHPStan static analysis
- Full test suite
- Code formatting (php-cs-fixer, Prettier)
- TypeScript type checking

5. **Review and understand all output** — Contributors are responsible for understanding and validating any AI-generated code before submitting. Do not submit code you don't understand.

6. **Document open questions** — When AI encounters ambiguity, log questions in `docs/specs/4-architecture/open-questions.md` and wait for clarification before proceeding.

### Recommended AI Models

We recommend using **Claude Sonnet or Claude Opus** for AI-assisted contributions. Avoid free-tier models from GitHub Copilot as they tend to hallucinate heavily and struggle to follow structured task files.

When using **Claude Code**, reference `@AGENTS.md` as the first step in your conversation to guide the agent through the SDD workflow.

### Two-PR Workflow (Recommended)

For SDD contributions, we recommend splitting your work into **two pull requests**:

1. **Specification PR** — Submit the spec, plan, and tasks files first. This allows maintainers to review the proposed approach before implementation begins.
2. **Implementation PR** — Once the specification is approved, submit the implementation that builds from the approved spec.

This workflow ensures that:
- The specification can be reviewed and refined before any code is written
- Time and resources are not wasted on implementations that don't align with project goals
- The spec serves as a clear contract for what the implementation should deliver

### Small Fixes Exception

The full SDD workflow is not required for trivial changes such as:
- Typo fixes
- Single-line bug fixes with obvious solutions
- Minor documentation corrections
- Simple configuration changes

For these cases, a direct PR without specifications is acceptable. Use your judgment — if the change requires design decisions or affects multiple files, use SDD.

### Why SDD with AI?

Specification-Driven Development ensures that AI-assisted contributions:
- Are traceable back to explicit requirements
- Follow established architectural patterns
- Maintain test coverage organically
- Don't introduce undocumented behavior or "magic" code

## Pull Request Guidelines

- Keep your changes focused and atomic
Expand Down Expand Up @@ -208,4 +270,4 @@ Thank you for contributing to Lychee! 🌸

---

*Last updated: December 22, 2025*
*Last updated: January 21, 2026*
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Documentation is organized following the [Diátaxis framework](https://diataxis.
### Contributing
- [Contribution Guide](Contribute.md) - How to contribute to Lychee
- [Coding Conventions](specs/3-reference/coding-conventions.md) - PHP and Vue3 coding standards
- [AI/Claude Guidelines](Contribute.md#using-aiclaude-for-contributions) - Guidelines for AI-assisted development
- [AGENTS.md](../AGENTS.md) - Instructions for AI agents working on this codebase

## Additional Resources

Expand All @@ -77,4 +79,4 @@ For more information about Lychee:

---

*Last updated: December 22, 2025*
*Last updated: January 21, 2026*
5 changes: 3 additions & 2 deletions docs/specs/1-concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ Album
├─ has children → Albums (self-referencing, nested tree)
├─ owned by → User (many-to-one)
├─ has → AccessPermissions (one-to-many)
├─ has → Statistics (one-to-one)
├─ has → AlbumSizeStatistics (one-to-one) - size/count aggregates
├─ has → Statistics (one-to-one) - visit/download tracking
└─ can be → Purchasable (one-to-one)
Photo
Expand Down Expand Up @@ -132,4 +133,4 @@ Now that you understand Lychee's core concepts:

---

*Last updated: December 22, 2025*
*Last updated: January 21, 2026*
30 changes: 28 additions & 2 deletions docs/specs/2-how-to/configure-pagination.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# How-To: Configure Album and Photo Pagination

**Author:** Lychee Team
**Last Updated:** 2026-01-10
**Last Updated:** 2026-01-21
**Feature:** 007-pagination
**Related:** [Feature 007 Spec](../4-architecture/features/007-pagination/spec.md)

Expand All @@ -20,7 +20,7 @@ Pagination improves performance and user experience for large galleries:

## Configuration Settings

Pagination is configured through four settings in the admin panel under **Settings > Gallery**.
Pagination is configured through six settings in the admin panel under **Settings > Gallery**.

### Page Size Settings

Expand Down Expand Up @@ -98,6 +98,32 @@ Controls how users load additional album pages. Same options as photos.
| Default | infinite_scroll |
| Location | Settings > Gallery |

### Infinite Scroll Threshold Settings

These settings control when the next page loads during infinite scroll mode.

#### photos_infinite_scroll_threshold

Controls how early to trigger loading the next page of photos.

| Property | Value |
|----------|-------|
| Type | Integer |
| Default | 2 |
| Location | Settings > Gallery |

The value represents the number of viewport heights from the bottom of the page at which to trigger loading. A higher value means earlier loading (preloading more content), but may load unnecessary data if the user doesn't scroll that far.

#### albums_infinite_scroll_threshold

Controls how early to trigger loading the next page of albums. Same behavior as photos threshold.

| Property | Value |
|----------|-------|
| Type | Integer |
| Default | 2 |
| Location | Settings > Gallery |

## Configuring via Admin Panel

1. Log in as an administrator
Expand Down
8 changes: 4 additions & 4 deletions docs/specs/2-how-to/sql-timeout-logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The max execution time is configured in `config/octane.php`:

### 1. Query Execution Logging

**Location**: [app/Providers/AppServiceProvider.php:238-300](app/Providers/AppServiceProvider.php#L238-L300)
**Location**: [app/Providers/AppServiceProvider.php:261-350](app/Providers/AppServiceProvider.php#L261-L350)

The `logSQL()` method logs queries after they complete with severity based on execution time:
- **Debug**: Normal slow queries (>100ms)
Expand All @@ -52,15 +52,15 @@ The `logSQL()` method logs queries after they complete with severity based on ex

**Location**: [app/Listeners/LogQueryTimeout.php](app/Listeners/LogQueryTimeout.php)

Registered in [app/Providers/EventServiceProvider.php:97-99](app/Providers/EventServiceProvider.php#L97-L99)
Registered in [app/Providers/EventServiceProvider.php:99](app/Providers/EventServiceProvider.php#L99)

This listener provides detailed logging for queries that exceed warning/critical thresholds:
- **70% threshold**: WARNING level log
- **90% threshold**: CRITICAL/ERROR level log

### 3. PHP Timeout Handler

**Location**: [app/Providers/AppServiceProvider.php:200-216](app/Providers/AppServiceProvider.php#L200-L216)
**Location**: [app/Providers/AppServiceProvider.php:204-220](app/Providers/AppServiceProvider.php#L204-L220)

A shutdown function that catches when PHP times out entirely, logging:
- Error message
Expand Down Expand Up @@ -125,7 +125,7 @@ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET SESSION wait_timeout=28800', // 8 hours

### Octane Database Ping

The AppServiceProvider pings database connections every 30 seconds to prevent timeouts: [app/Providers/AppServiceProvider.php:340-341](app/Providers/AppServiceProvider.php#L340-L341)
The AppServiceProvider pings database connections every 30 seconds to prevent timeouts: [app/Providers/AppServiceProvider.php:409-424](app/Providers/AppServiceProvider.php#L409-L424)

## Viewing Logs

Expand Down
4 changes: 2 additions & 2 deletions docs/specs/2-how-to/translating-lychee.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ return [
Ensure consistency across all languages:

```bash
php artisan test --filter TranslationTest
php artisan test --filter LangTest
```

### Best Practices for Developers
Expand Down Expand Up @@ -217,4 +217,4 @@ This localization system ensures Lychee remains accessible to users worldwide wh

---

*Last updated: December 22, 2025*
*Last updated: January 21, 2026*
50 changes: 43 additions & 7 deletions docs/specs/2-how-to/using-renamer.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ The Renamer module allows you to automatically transform filenames during import
To create a basic replacement rule:

1. Access the Renamer API or use the admin interface
2. Define the pattern to find (needle)
3. Specify the replacement text
4. Choose the replacement mode (FIRST, ALL, or REGEX)
2. Define the pattern to find (needle) - not needed for case/trim modes
3. Specify the replacement text - not needed for case/trim modes
4. Choose the replacement mode (FIRST, ALL, REGEX, TRIM, LOWER, UPPER, UCWORDS, or UCFIRST)
5. Set the processing order
6. Enable the rule
7. Optionally set `is_photo_rule` and/or `is_album_rule` to control where the rule applies

### Example: Replace Camera Prefix

Expand All @@ -31,13 +32,15 @@ Replace `IMG_` with `Photo_`:
```php
$rule = new RenamerRule();
$rule->owner_id = Auth::id();
$rule->name = 'Replace IMG_';
$rule->rule = 'Replace IMG_';
$rule->description = 'Replaces IMG_ with Photo_';
$rule->needle = 'IMG_';
$rule->replacement = 'Photo_';
$rule->mode = RenamerModeType::FIRST; // Only replace first occurrence
$rule->order = 1;
$rule->is_enabled = true;
$rule->is_photo_rule = true; // Apply to photo filenames
$rule->is_album_rule = false; // Don't apply to album titles
$rule->save();
```

Expand All @@ -50,7 +53,7 @@ Replace all underscores with spaces:
```php
$rule = new RenamerRule();
$rule->owner_id = Auth::id();
$rule->name = 'Underscores to Spaces';
$rule->rule = 'Underscores to Spaces';
$rule->description = 'Replace all underscores with spaces';
$rule->needle = '_';
$rule->replacement = ' ';
Expand All @@ -69,7 +72,7 @@ Use regex to add a date prefix:
```php
$rule = new RenamerRule();
$rule->owner_id = Auth::id();
$rule->name = 'Add Date Prefix';
$rule->rule = 'Add Date Prefix';
$rule->description = 'Extract date from filename and move to beginning';
$rule->needle = '/^(.+)_(\d{4}-\d{2}-\d{2})(.+)$/';
$rule->replacement = '$2_$1$3';
Expand All @@ -81,6 +84,39 @@ $rule->save();

**Result**: `vacation_2024-06-15_beach.jpg` becomes `2024-06-15_vacation_beach.jpg`

### Example: Transform Case

Use case transformation modes (needle/replacement are ignored for these):

```php
// Convert to lowercase
$rule = new RenamerRule();
$rule->owner_id = Auth::id();
$rule->rule = 'Lowercase';
$rule->description = 'Convert filename to lowercase';
$rule->mode = RenamerModeType::LOWER;
$rule->order = 4;
$rule->is_enabled = true;
$rule->save();
```

**Result**: `VACATION_Photo.jpg` becomes `vacation_photo.jpg`

### Example: Trim Whitespace

```php
$rule = new RenamerRule();
$rule->owner_id = Auth::id();
$rule->rule = 'Trim spaces';
$rule->description = 'Remove leading/trailing whitespace';
$rule->mode = RenamerModeType::TRIM;
$rule->order = 5;
$rule->is_enabled = true;
$rule->save();
```

**Result**: ` photo name.jpg ` becomes `photo name.jpg`

## Applying Patterns

### Single Filename
Expand Down Expand Up @@ -259,4 +295,4 @@ Ensure:

---

*Last updated: December 22, 2025*
*Last updated: January 21, 2026*
4 changes: 3 additions & 1 deletion docs/specs/3-reference/api-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ Page sizes and UI modes are configurable via the admin settings panel or directl
| photos_per_page | integer (1-1000) | 100 | Number of photos per page |
| albums_pagination_ui_mode | enum | infinite_scroll | UI mode for album pagination |
| photos_pagination_ui_mode | enum | infinite_scroll | UI mode for photo pagination |
| albums_infinite_scroll_threshold | integer | 2 | Viewport heights from bottom to trigger album loading |
| photos_infinite_scroll_threshold | integer | 2 | Viewport heights from bottom to trigger photo loading |

**UI Mode Options:**
- `infinite_scroll` - Auto-load next page on scroll (default)
Expand Down Expand Up @@ -324,4 +326,4 @@ Page sizes and UI modes are configurable via the admin settings panel or directl

---

*Last updated: January 10, 2026*
*Last updated: January 21, 2026*
14 changes: 7 additions & 7 deletions docs/specs/3-reference/database-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ Individual photos with metadata, EXIF data, and file information.
- `id`: Primary key
- `title`: Photo title
- `description`: Optional description
- `album_id`: Foreign key to Album (nullable)
- `owner_id`: Foreign key to User
- `type`: MIME type
- `original_checksum`: SHA-256 checksum
Expand All @@ -93,13 +92,13 @@ Individual photos with metadata, EXIF data, and file information.
- `latitude`, `longitude`: GPS coordinates

**Relationships:**
- Belongs to `Album`
- Belongs to many `Album` through `photo_album` pivot table (many-to-many)
- Belongs to `User` (owner)
- Has many `SizeVariant`
- Has one `Palette`
- Has many `Tag` through `photo_tag` pivot table
- Has many `Tag` through `photos_tags` pivot table
- Has many `PhotoRating`
- Has one `PhotoStatistics` (virtual relationship for aggregated metrics)
- Has one `Statistics` (visit/download tracking)

#### SizeVariant
Different size versions of photos (original, medium, small, thumb).
Expand Down Expand Up @@ -228,10 +227,11 @@ This dual approach allows Lychee to provide:
- **Users**: Own albums and photos, belong to user groups (SE edition)

### Many-to-Many
- **Tags**: Many-to-many with photos through `photo_tag` pivot table
- **Photos-Albums**: Many-to-many through `photo_album` pivot table (photos can belong to multiple albums)
- **Tags**: Many-to-many with photos through `photos_tags` pivot table

### One-to-Many
- **Photos**: Belong to one album, owned by one user
- **Photos**: Owned by one user (but can belong to multiple albums)
- **Size Variants**: Multiple variants per photo
- **Access Permissions**: Multiple permissions per album

Expand All @@ -258,4 +258,4 @@ Eager loading enforced with `Model::shouldBeStrict()`, which throws an exception

---

*Last updated: December 22, 2025*
*Last updated: January 21, 2026*
Loading