Files
iu 14db79632a chore(openspec): архивировать android-live-albums и fix-album-cover-readiness
Дельта android-live-albums: android-client (+3 требования: живое обновление
списка, догон при переподключении, потеря доступа) и realtime (порядок списка
теперь правило отображения клиента).

Дельта fix-album-cover-readiness: realtime (+1 требование: album.updated при
готовности вариантов фото-обложки).
2026-10-16 12:48:32 +03:00

66 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 не меняются, нужен только перезапуск бэкенда (логика и
статика в одном бинарнике). Откат — предыдущий бинарник.