# Spec: Image Processing Refactor

## Context

Current image handling uses on-demand resize via `/uploads-resized/` route. Admin uploads save originals as-is (jpg/png/webp), and resize happens on first request. User uploads (reviews, 2MB limit) also use on-demand resize.

## Goals

1. **User images (reviews, ≤2MB):** Improve size error messages to be user-friendly
2. **Admin images (products, ≤20MB):** Eager processing at upload time
   - Generate all resize widths (320, 640, 1024, 1600) in AVIF + WebP
   - Convert original to WebP (delete source file)
   - Full-screen viewer shows original in WebP (no width limit)
   - Thumbnails use resized versions from cache

## Architecture

### Server Changes

#### 1. `server/src/lib/upload-images.js`
- Add `eager` parameter to `persistMultipartImages`
- When `eager: true`, after saving each file:
  1. Call `generateAllSizes(uuid, subdir, fullPath)` — generates all sizes from original
  2. Call `convertOriginalToWebp(uuid, subdir)` — converts original to WebP, deletes source
  3. Update URL to use `.webp` extension (replace original extension)

#### 2. `server/src/lib/image-resize.js`
- Add `generateAllSizes(uuid, subdir, originalPath)`:
  - For each width in [320, 640, 1024, 1600]:
    - Generate AVIF and WebP in `.cache/<subdir>/`
  - Uses original file path (before conversion to WebP)
- Add `convertOriginalToWebp(uuid, subdir)`:
  - Find original file (jpg/png)
  - Convert to WebP (quality 80) at same location with `.webp` extension
  - Delete original jpg/png file
  - Return new `.webp` path

#### 3. `server/src/routes/api/admin-products.js`
- Pass `eager: true` to `persistMultipartImages`

#### 4. `server/src/routes/api/public-reviews.js`
- Improve error message for file too large (413)

### Client Changes

#### 1. `client/src/entities/product/api/product-api.ts`
- Add pre-upload size check for review images
- Clear error message: "Файл «<name>» слишком большой (максимум 2 МБ)"

#### 2. `client/src/shared/ui/OptimizedImage.tsx`
- Update `buildSrcSet` to use cached AVIF/WebP directly
- Full-screen viewer: use original `.webp` URL (no `?w=`)
- Remove fallback to original format for upload URLs

#### 3. `client/src/features/product-review/ui/ReviewDialog.tsx`
- Show user-friendly error message for oversized files

## Data Flow

### Admin Upload (Eager)
1. Client sends FormData to `POST /api/admin/uploads`
2. Server saves original (e.g., `uuid.jpg`)
3. Server generates all sizes in `.cache/` from original
4. Server converts original to WebP (`uuid.webp`), deletes `uuid.jpg`
5. Returns URLs with `.webp` extension (e.g., `/uploads/<uuid>.webp`)
6. Client displays using OptimizedImage with srcset from cache

### User Upload (Reviews)
1. Client validates file size ≤2MB before upload
2. Server validates and saves original
3. On-demand resize still works (existing flow)
4. Clear error messages at both client and server

## Error Handling

### User Upload Size Error
- **Client:** Pre-upload check with message "Файл «<name>» слишком большой (максимум 2 МБ)"
- **Server:** 413 with "Файл слишком большой (максимум 2 МБ)"

### Admin Upload Processing Error
- If sharp fails: return 500 with "Ошибка обработки изображения"
- If file not found after save: return 500 with "Внутренняя ошибка сервера"

## Testing

### Server Tests
- Test `generateAllSizes` creates all width+format combinations
- Test `convertOriginalToWebp` converts and deletes original
- Test `persistMultipartImages` with `eager: true`
- Test error messages for oversized files

### Client Tests
- Test pre-upload size validation for reviews
- Test OptimizedImage srcset generation for WebP originals
- Test error message display in ReviewDialog
