# Design ## Context См. `proposal.md` — мотивация. Что важно для подхода: - Ссылки на обложку формируются из `photo_objects` (роли `thumb`/`medium`), которые заполняются асинхронно, поэтому в объекте альбома (`Store.GetByID` джойнит обложку) они появляются не в момент загрузки, а когда варианты готовы. - Загрузка фото вызывает `SetCoverIfEmpty`, то есть первое фото становится обложкой ещё до генерации вариантов, а `NotifyAlbumChanged` на этом шаге публикует `album.updated` уже с `cover_photo_id`, но с пустыми ссылками. - Готовность вариантов обрабатывает `Worker.notify` (`internal/photos/worker.go`): он публикует только `photo.updated` в канал альбома и персональные потоки не трогает. - `albums.Service` уже умеет перечитать альбом, обогатить обложку (`attachCover`) и веерно опубликовать `album.updated` (`NotifyAlbumChanged` → `PublishAlbumTo`). ## Goals / Non-Goals **Goals:** - Доставить актуальную ссылку на обложку в персональные потоки, когда она появляется. - Не увеличивать поток событий при массовой загрузке. **Non-Goals:** - Менять контракт `photo.updated` в канале альбома (открытый экран закрывает свой случай сам). - Клиентские обходные пути (рефетч списка по таймеру и т.п.). - Синхронная генерация вариантов при загрузке. ## Decisions **1. Публикует `albums.Service`, а не воркер.** Решение «объект альбома изменился» принимает владелец альбомов: добавляется `NotifyCoverReady(ctx, albumID, photoID)`, который перечитывает альбом и публикует `album.updated`, только если `cover_photo_id == photoID` (иначе no-op), переиспользуя `NotifyAlbumChanged`. Воркер остаётся про фото и лишь сообщает о готовности. Альтернатива — строить `album.updated` прямо в воркере: тогда `photos` пришлось бы знать форму объекта альбома, обложку и аудиторию. **2. Условие — совпадение с текущей обложкой.** Готовность вариантов обычного фото объект альбома не меняет. Без условия пришлось бы на каждое фото перечитывать альбом и веерно публиковать `album.updated` всем участникам — лишняя нагрузка при массовой загрузке и лишние события клиентам. Альтернатива — «публиковать всегда»: проще, но шумно; отвергнуто. **3. Проверка по `cover_photo_id`, а не по «в альбоме ещё нет ссылки на обложку».** Так закрываются оба сценария: и автопоставленная обложка первой загрузкой, и явная установка обложки на фото, варианты которого ещё не готовы. ## Risks / Trade-offs - **Двойное чтение альбома** (`GetByID` для проверки обложки и внутри `PublishAlbumTo`) → дополнительное чтение точечное (только для готовности вариантов фото-обложки), принято; при необходимости схлопывается в один публикующий вызов. - **Гонка со сменой обложки** между публикацией `photo.created` и готовностью вариантов → проверка по актуальному `cover_photo_id` отсекает лишнюю публикацию, а новая обложка получит своё событие при готовности её вариантов. - **Аудитория недоступна** (ошибка в `members.Audience`) → событие не публикуется; клиент догоняет состояние при переподключении (`refresh`) — существующее поведение веера. ## Migration Plan Изменение аддитивное: схема и API не меняются, нужен только перезапуск бэкенда (логика и статика в одном бинарнике). Откат — предыдущий бинарник.