# Tasks ## 1. Клиент персонального потока - [x] 1.1 Добавить в `GaloApi` запрос персонального потока (`api/sse/me`, `Accept: text/event-stream`); проверить request-тестом, что URL собран от базового адреса и заголовок выставлен - [x] 1.2 Добавить парсер событий жизненного цикла альбома (`album.created`, `album.updated`, `album.deleted` -> типы с объектом/идентификатором; `notification` и неизвестные -> игнорировать); проверить unit-тестами на каждый тип, на неизвестный тип и на битый JSON - [x] 1.3 Добавить `PersonalSseClient` по образцу `AlbumSseClient` (backoff, событие `Reconnected` после разрыва); проверить тестом: события доходят в порядке получения, после разрыва приходит `Reconnected` - [x] 1.4 Проверить завершение соединения при отмене коллектора (отменяется ли `EventSource`, когда поток перестают собирать), при необходимости закрывать его в `finally`/`invokeOnClose`; проверить тестом, что после отмены поток не переоткрывается ## 2. Применение событий к кэшу - [x] 2.1 Вывести удаление в `AlbumLocalSource` (`deleteByIds`) и `PhotoLocalSource` (очистка фото альбома); проверить тестами локального источника, что записи удаляются - [x] 2.2 Добавить в `AlbumRepository` применение событий: `album.created`/`album.updated` заменяют альбом, `album.deleted` удаляет альбом вместе с его фото; проверить тестами на фейках: обновлённый объект виден из `observeAlbums`, удалённый исчезает вместе со своими фото - [x] 2.3 Связать клиент персонального потока с репозиторием в `AppContainer` и обработчиком событий; проверить тестом, что событие из потока меняет то, что отдаёт `observeAlbums` ## 3. Подписка на время сессии - [x] 3.1 Подписаться на персональный поток в `AlbumsViewModel` и завершать подписку при уничтожении ViewModel; проверить тестом, что подписка создаётся один раз и не пересоздаётся при повторном обращении к состоянию - [x] 3.2 На `Reconnected` вызывать `albumRepository.refresh()`; проверить тестом, что альбом, удалённый на сервере за время разрыва, исчезает из кэша после догона - [x] 3.3 Обновить `android/README.md`, раздел «Live updates»: персональный поток, обновление списка и потеря доступа; проверить, что описанный эндпоинт совпадает с реализованным запросом ## 4. Потеря доступа к альбому - [x] 4.1 Отслеживать в `AlbumViewModel` факт «альбом уже был показан» и отдавать сигнал потери доступа при переходе «ненулевой -> отсутствует»; проверить unit-тестами оба случая: был и исчез — сигнал есть, не было изначально (нет кэша, нет сети) — сигнала нет - [x] 4.2 По сигналу потери доступа экран альбома возвращается к списку; проверить Compose-тестом (Robolectric), что экран инициирует возврат, а список больше не содержит альбом - [x] 4.3 Проверить сценарий исключения участника: `album.deleted` приходит только личным потоком, альбом исчезает из списка и открытый экран уходит назад ## 5. Интеграционная проверка - [x] 5.1 Прогнать сквозной сценарий на двух аккаунтах против локального сервера: создание, переименование, смена обложки, удаление альбома и исключение участника — список и открытый экран обновляются без ручного обновления; приложить результат (шаги и наблюдение) - [x] 5.2 Проверить поведение при истечении токена в долгоживущем потоке: соединение восстанавливается после обновления токена; если нет — зафиксировать ограничение в README - [x] 5.3 Прогнать на финальной ревизии `cd android && ./gradlew test` и `cd android && ./gradlew :app:assembleDebug`; зафиксировать, что обе команды проходят - [x] 5.4 Прогонять unit-тесты только на debug-варианте (тестовая активность Compose приходит из debug-зависимости `ui-test-manifest`), чтобы `cd android && ./gradlew test` проходил без тестовой активности в release-манифесте; проверить `./gradlew test`, `:app:assembleDebug` и `:app:assembleRelease` ## Workflow follow-up - Заархивировать change после прохождения ревью проекта (`openspec archive android-live-albums`). - Проверить, что после архивации требования вошли в `openspec/specs/android-client/spec.md` и `openspec/specs/realtime/spec.md`, а `openspec validate --strict android-live-albums` не выдаёт ошибок.