ebae775f3f504e95a36f75d27d3dddf79b0787cc
Adds the spec §6.10 heaps concept: named photo collections with a
single "active" target for fast keyboard adds. Uses a basket icon
(ShoppingBasket) to visually distinguish heaps from folders.
Backend (routers/heaps.py)
- Replaces the 27-line stub with full CRUD: list (with photo counts
via a single LEFT JOIN), create, patch (rename + set active), delete.
- Add/remove photos endpoints with idempotent semantics: re-adding an
existing member is a no-op, removing a non-member is a no-op.
- Setting is_active=true on one heap clears the flag on every other
heap in a single UPDATE so we maintain the single-active invariant.
- routers/photos.py list endpoint now applies the heap_id filter via
IN-subquery against heap_photos (it was a declared param but had
no filter logic).
Frontend
- New hooks/useHeapsQuery.ts and useFilterUrlSync wires heap_id as
another URL-persisted filter; usePhotosQuery threads it through.
- New components/heaps/HeapsPanel.tsx replaces the LeftSidebar Heaps
stub. Shows the basket icon, photo counts, lets you create heaps
inline, click to filter the timeline, set active via the target
icon, and delete heaps.
- TopBar shows an "active heap" pill (basket + name) so the user
always knows where the next T-press will land.
- KeyboardHints adds T → Add to heap.
T shortcut (useKeyboardShortcuts)
- Reads the active heap from the heaps query cache and the selection
from the photo store at fire time. Adds the selected photos (or the
active photo if nothing is selected) via POST /heaps/{id}/photos.
- Toasts:
- "Added to {heap}: N photos (M already present)" on success
- "No active heap" hint when none is set
- "Nothing selected" hint when there's no selection or active photo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mulita - Self-Hosted Photo Management Application
A self-hosted, Docker-deployed photo management application inspired by Lightroom's workflow. Mulita provides a fast, keyboard-driven interface to browse, organize, tag, and manage your photo library.
Features
- Photo Organization: Browse photos in a timeline view with virtual scrolling for performance
- Thumbnail Generation: Automatic thumbnail generation for all photo formats including RAW
- Metadata Extraction: Full EXIF/XMP metadata extraction and search
- Keyboard Shortcuts: Lightroom-style keyboard navigation and actions
- File Support: JPEG, PNG, RAW formats (CR2, CR3, NEF, ARW, etc.), HEIC/HEIF, and videos
- Heaps: Temporary collections for organizing photos
- Tags & Ratings: Organize with tags, star ratings, and color labels
- Dark Mode: Photography-optimized dark interface
Tech Stack
Backend
- Python 3.12 with FastAPI
- SQLite with SQLAlchemy (async)
- Celery + Redis for background tasks
- pyvips for fast thumbnail generation
- ExifTool for metadata extraction
Frontend
- React 18 with TypeScript
- Vite for fast development
- TanStack Query for data fetching
- TanStack Virtual for virtualized scrolling
- Tailwind CSS for styling
- Zustand for state management
Quick Start
Prerequisites
- Docker and Docker Compose
- Photo directories to mount
Setup
- Clone the repository:
git clone <repository-url>
cd muleimage
- Configure your photo directories in
.env:
# Edit .env file
PHOTO_DIRS=/path/to/your/photos
- Start the application:
docker-compose up -d
- Access the application at
http://localhost:3000
Architecture
The application consists of 5 Docker services:
- frontend: React SPA served by Nginx
- backend: FastAPI REST API
- worker: Celery workers for background tasks
- redis: Message broker for Celery
- db: SQLite database (file-based)
Keyboard Shortcuts
| Key | Action |
|---|---|
← → ↑ ↓ |
Navigate photos |
Space |
Quick preview |
Enter |
Open loupe view |
P |
Pick photo |
X |
Reject photo |
1-5 |
Set star rating |
Tab |
Toggle left sidebar |
I |
Toggle metadata panel |
G |
Grid view |
E |
Loupe view |
Delete |
Move to trash |
Development
Backend Development
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload
Frontend Development
cd frontend
npm install
npm run dev
Configuration
Edit mulita.yml to configure:
- Source photo directories
- Thumbnail sizes and quality
- Scanner settings
- Performance tuning
Performance
- Handles 100,000+ photos efficiently
- Virtual scrolling for smooth timeline navigation
- Thumbnail generation at 10+ photos/second
- SQLite FTS5 for fast full-text search
Future Features (Phase 2)
- AI-powered scene classification
- Face detection and clustering
- Smart albums
- Duplicate detection
- Export presets
- Multi-user support
License
MIT
Description
Languages
Svelte
54.6%
TypeScript
26.5%
Go
18.2%
CSS
0.4%
Dockerfile
0.1%
Other
0.1%