fix(photos): faster large-album loading and album cover lifecycle

Large albums opened slowly and flickered; the default album cover was
also lost in the media-objects refactor.

Large-album loading:
- quantize signed object URLs within a window and send Cache-Control so
  previews are reused instead of re-fetched
- publish photo.updated (not photo.created) when variants are ready, so
  clients update in place without reordering the grid
- worker concurrency + newest-first variant priority; raise default batch

Album cover:
- first uploaded photo becomes the cover when none is set
- clear the cover when the cover photo is trashed or moved out
- "set as album cover" in the photo context menu and actions panel

OpenSpec changes: fix-large-album-load, fix-album-cover
This commit is contained in:
iu
2026-10-11 14:15:59 +03:00
parent 6361e48b20
commit d7a37328f0
40 changed files with 999 additions and 57 deletions
@@ -0,0 +1,24 @@
# Tasks
## 1. Стабильные подписанные URL и кэширование объектов
- [x] 1.1 Добавить параметр окна подписи (`OBJECT_URL_SIGN_WINDOW`, по умолчанию 10m) в `internal/config/config.go` и квантовать срок в `Signer.URL` (`exp = now.Truncate(window) + ttl`); проверить `cd backend && go test ./internal/storage/` — новый тест подтверждает, что URL совпадает в пределах окна и меняется за его границей
- [x] 1.2 Выставить `Cache-Control` для ответов объектов в `internal/app/app.go` (`serveObject`), используя параметр срока свежести; проверить `cd backend && go test ./internal/app/` — тест утверждает наличие заголовка в ответе
- [x] 1.3 Задокументировать новые переменные окружения в `.env.example` и проверить, что описанные значения совпадают с дефолтами в `config.go`
## 2. Семантика событий готовности вариантов
- [x] 2.1 Переключить уведомление worker'а на `photo.updated` в `internal/photos/worker.go` (`notify`), оставив `photo.created` только для реального появления фото; проверить тестом с подпиской на hub, что после генерации вариантов публикуется `photo.updated`
- [x] 2.2 Добавить `photo.updated` в `knownEvents` в `frontend/src/lib/sse.ts`; проверить `cd frontend && npm run check`
- [x] 2.3 В `frontend/src/routes/(app)/albums/[id]/+page.svelte` сделать `photo.updated` заменой только уже присутствующих фото, а `photo.created` — по-прежнему добавлением; проверить `npm run check` и наблюдаемым поведением: событие обновления для отсутствующего фото не добавляет его в сетку
## 3. Пропускная способность и приоритет очереди вариантов
- [x] 3.1 Добавить `WORKER_CONCURRENCY` в `internal/config/config.go` и пул параллельной обработки в `internal/photos/worker.go`; проверить `cd backend && go test ./internal/photos/` — тест подтверждает обработку не менее N заданий за проход при concurrency > 1
- [x] 3.2 Изменить порядок в `ClaimVariantJobs` (`internal/photos/sqlite_objects.go`): свежие задания — по свежести фото (`created_at DESC`), задания с ошибками (`attempts > 0`) — по `next_attempt_at`, сохраняя backoff; проверить тестом на порядок выдачи
- [x] 3.3 Задокументировать `WORKER_CONCURRENCY` и пороги очереди в `.env.example`; проверить соответствие дефолтов `config.go`
## 4. Интеграционная проверка
- [x] 4.1 Собрать (`make build`), запустить и открыть существующий большой альбом (`seed-1791714157-0 #2`): первый экран заполняется превью первыми, сетка не перетасовывается при доготовке вариантов; подтвердить повторным открытием без перекачки неизменившихся превью (Network)
- [x] 4.2 Прогнать полный набор: `cd backend && go test ./...` и `cd frontend && npm run check` — оба зелёные; замерить рост пропускной способности по убыванию `variant_jobs`