From cc85d792ea5562606e907183a1da2119bc816d96 Mon Sep 17 00:00:00 2001 From: Iu Date: Fri, 16 Oct 2026 23:20:04 +0300 Subject: [PATCH] =?UTF-8?q?chore(openspec):=20=D0=B0=D1=80=D1=85=D0=B8?= =?UTF-8?q?=D0=B2=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20phone-album-?= =?UTF-8?q?two-way-sync=20=D0=B8=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B5=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Дельта-спеки перенесены в основные: phone-album-sync (+8 требований, 6 изменено, 1 удалено — «Удаление только собственных загрузок»), android-client. Change перемещён в openspec/changes/archive/2026-10-16-phone-album-two-way-sync. --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/android-client/spec.md | 0 .../specs/phone-album-sync/spec.md | 0 .../tasks.md | 0 openspec/specs/android-client/spec.md | 29 ++- openspec/specs/phone-album-sync/spec.md | 220 +++++++++++++++--- 8 files changed, 214 insertions(+), 35 deletions(-) rename openspec/changes/{phone-album-two-way-sync => archive/2026-10-16-phone-album-two-way-sync}/.openspec.yaml (100%) rename openspec/changes/{phone-album-two-way-sync => archive/2026-10-16-phone-album-two-way-sync}/design.md (100%) rename openspec/changes/{phone-album-two-way-sync => archive/2026-10-16-phone-album-two-way-sync}/proposal.md (100%) rename openspec/changes/{phone-album-two-way-sync => archive/2026-10-16-phone-album-two-way-sync}/specs/android-client/spec.md (100%) rename openspec/changes/{phone-album-two-way-sync => archive/2026-10-16-phone-album-two-way-sync}/specs/phone-album-sync/spec.md (100%) rename openspec/changes/{phone-album-two-way-sync => archive/2026-10-16-phone-album-two-way-sync}/tasks.md (100%) diff --git a/openspec/changes/phone-album-two-way-sync/.openspec.yaml b/openspec/changes/archive/2026-10-16-phone-album-two-way-sync/.openspec.yaml similarity index 100% rename from openspec/changes/phone-album-two-way-sync/.openspec.yaml rename to openspec/changes/archive/2026-10-16-phone-album-two-way-sync/.openspec.yaml diff --git a/openspec/changes/phone-album-two-way-sync/design.md b/openspec/changes/archive/2026-10-16-phone-album-two-way-sync/design.md similarity index 100% rename from openspec/changes/phone-album-two-way-sync/design.md rename to openspec/changes/archive/2026-10-16-phone-album-two-way-sync/design.md diff --git a/openspec/changes/phone-album-two-way-sync/proposal.md b/openspec/changes/archive/2026-10-16-phone-album-two-way-sync/proposal.md similarity index 100% rename from openspec/changes/phone-album-two-way-sync/proposal.md rename to openspec/changes/archive/2026-10-16-phone-album-two-way-sync/proposal.md diff --git a/openspec/changes/phone-album-two-way-sync/specs/android-client/spec.md b/openspec/changes/archive/2026-10-16-phone-album-two-way-sync/specs/android-client/spec.md similarity index 100% rename from openspec/changes/phone-album-two-way-sync/specs/android-client/spec.md rename to openspec/changes/archive/2026-10-16-phone-album-two-way-sync/specs/android-client/spec.md diff --git a/openspec/changes/phone-album-two-way-sync/specs/phone-album-sync/spec.md b/openspec/changes/archive/2026-10-16-phone-album-two-way-sync/specs/phone-album-sync/spec.md similarity index 100% rename from openspec/changes/phone-album-two-way-sync/specs/phone-album-sync/spec.md rename to openspec/changes/archive/2026-10-16-phone-album-two-way-sync/specs/phone-album-sync/spec.md diff --git a/openspec/changes/phone-album-two-way-sync/tasks.md b/openspec/changes/archive/2026-10-16-phone-album-two-way-sync/tasks.md similarity index 100% rename from openspec/changes/phone-album-two-way-sync/tasks.md rename to openspec/changes/archive/2026-10-16-phone-album-two-way-sync/tasks.md diff --git a/openspec/specs/android-client/spec.md b/openspec/specs/android-client/spec.md index c2a3d78..3b6470f 100644 --- a/openspec/specs/android-client/spec.md +++ b/openspec/specs/android-client/spec.md @@ -110,7 +110,8 @@ Android-клиент для self-hosted galo: настройка подключ Приложение SHALL открывать альбом устройства с сеткой медиа и SHALL предоставлять настройки синхронизации этого альбома по свайпу вправо. Настройки SHALL содержать -только выбор режима синхронизации. +только выбор режима синхронизации. Сетка SHALL включать как локальные элементы, так и +скачанные с сервера при двусторонней синхронизации. #### Scenario: Открытие альбома устройства @@ -132,6 +133,11 @@ Android-клиент для self-hosted galo: настройка подключ - **WHEN** серверной копии разгруженного элемента больше нет в целевом альбоме - **THEN** в сетке на элементе показывается перечёркнутый значок облака +#### Scenario: Скачанный элемент в сетке + +- **WHEN** серверное фото материализовано на устройстве +- **THEN** элемент показывается в сетке альбома устройства наряду с локальными + ### Requirement: Доступ к альбомам устройства Приложение SHALL запрашивать доступ к медиа устройства, нужный для перечисления @@ -734,3 +740,24 @@ SHALL показывать значками расположение содер - **WHEN** восстановление оригиналов завершилось - **THEN** ход больше не показывается, а карточка возвращается к метке режима + +### Requirement: Серверный состав в сетке альбома устройства + +Сетка альбома устройства SHALL отражать состав целевого серверного альбома: новые +серверные фото SHALL появляться, а удалённые SHALL исчезать по мере синхронизации, без +ручного обновления. + +#### Scenario: Новое фото с сервера + +- **WHEN** в целевом серверном альбоме появилось новое фото, и синхронизация прошла +- **THEN** материализованный элемент появляется в сетке альбома устройства + +#### Scenario: Фото удалено на сервере + +- **WHEN** серверное фото удалено на сервере, и синхронизация прошла +- **THEN** соответствующий элемент исчезает из сетки альбома устройства + +#### Scenario: Обновление открытого экрана + +- **WHEN** экран альбома устройства открыт, и синхронизация изменила состав +- **THEN** сетка обновляется без ручного действия пользователя diff --git a/openspec/specs/phone-album-sync/spec.md b/openspec/specs/phone-album-sync/spec.md index 484bba8..ebd6fd0 100644 --- a/openspec/specs/phone-album-sync/spec.md +++ b/openspec/specs/phone-album-sync/spec.md @@ -108,50 +108,42 @@ API, а изменение содержимого SHALL трактовать к - **WHEN** содержимое ассета уже присутствует на сервере (в том числе загружено ранее) - **THEN** приложение связывает ассет с существующим серверным фото без загрузки -### Requirement: Удаление только собственных загрузок - -При удалении содержимого на устройстве приложение SHALL удалять на сервере только -фото, загруженные этим клиентом, и SHALL сохранять остальные фото целевого альбома, -в том числе загруженные из других источников. - -#### Scenario: Собственная загрузка - -- **WHEN** удалено содержимое, которое загрузил этот клиент, и его контрольная сумма отсутствует во всех локальных альбомах, льющихся в этот серверный -- **THEN** приложение удаляет соответствующее серверное фото - -#### Scenario: Чужие фото - -- **WHEN** в целевом серверном альбоме есть фото, загруженные из других источников -- **THEN** приложение не удаляет их - ### Requirement: Отражение серверных изменений на устройстве -Приложение SHALL отражать изменения целевого серверного альбома — новые фото, -переименования, теги, подписи и удаления — в локальном представлении, SHALL NOT -изменять или удалять оригиналы на устройстве. +Приложение SHALL приводить локальное представление целевого серверного альбома в +соответствие с его составом: новое серверное фото SHALL материализоваться на устройстве +(оригинал в режиме копий, превью в режиме переноса), переименования, теги и подписи SHALL +отражаться, а серверное удаление SHALL убирать локальный элемент. Удаление снятого на +устройстве оригинала SHALL учитывать права сервера и системное согласие. #### Scenario: Новое с другого устройства - **WHEN** другое устройство добавляет фото в целевой серверный альбом -- **THEN** приложение показывает его в серверном представлении альбома +- **THEN** приложение материализует его в альбоме устройства — оригинал в режиме копий, превью в режиме переноса #### Scenario: Удалено на сервере - **WHEN** серверное фото удалено на сервере -- **THEN** приложение убирает его из локального представления, не трогая оригинал на устройстве +- **THEN** приложение убирает соответствующий локальный элемент, а для снятого на устройстве оригинала запрашивается системное согласие на удаление ### Requirement: Разгрузка оригиналов в режиме переноса -В режиме C после успешной загрузки приложение SHALL автоматически заменять оригинал -на устройстве превью, без отдельного действия на экране; видео SHALL заменяться -постером-кадром. При включении режима C приложение SHALL запрашивать широкий доступ -к файлам, чтобы замена выполнялась без системного диалога согласия. +В режиме C после того, как оригинал элемента оказывается на сервере (загружен этим +клиентом или скачан при двусторонней синхронизации), приложение SHALL автоматически +заменять оригинал на устройстве превью, без отдельного действия на экране; видео SHALL +заменяться постером-кадром. При включении режима C приложение SHALL запрашивать широкий +доступ к файлам, чтобы замена выполнялась без системного диалога согласия. #### Scenario: Разгрузка фото - **WHEN** оригинал фото успешно загружен - **THEN** файл в галерее автоматически заменяется превью с сохранением даты съёмки и места в альбоме +#### Scenario: Разгрузка скачанного оригинала + +- **WHEN** альбом переключён в режим переноса, а оригинал элемента был скачан с сервера +- **THEN** оригинал автоматически заменяется превью + #### Scenario: Разгрузка видео - **WHEN** оригинал видео успешно загружен @@ -164,8 +156,9 @@ API, а изменение содержимого SHALL трактовать к ### Requirement: Провенанс разгруженных превью -Разгруженные превью и постеры видео SHALL содержать метаданные провенанса, -достаточные для восстановления связи с серверным альбомом и принадлежности +Разгруженные превью, постеры видео и скачанные с сервера элементы SHALL содержать +метаданные провенанса, включая признак происхождения (загружено клиентом или скачано с +сервера), достаточные для восстановления связи с серверным альбомом и принадлежности загрузок после переустановки приложения. #### Scenario: Восстановление после переустановки @@ -173,6 +166,11 @@ API, а изменение содержимого SHALL трактовать к - **WHEN** приложение переустановлено или его данные очищены, а альбом устройства содержит разгруженные превью - **THEN** приложение восстанавливает связь с серверным альбомом и принадлежность своих загрузок из метаданных превью +#### Scenario: Метка скачанного элемента + +- **WHEN** метка провенанса восстанавливается у элемента, скачанного с сервера +- **THEN** элемент восстанавливается как скачанный, а не как собственная загрузка клиента + #### Scenario: Метка утрачена - **WHEN** метаданные провенанса недоступны (например, файл перезаписан сторонним редактором) @@ -182,7 +180,10 @@ API, а изменение содержимого SHALL трактовать к При выходе из режима C приложение SHALL предложить вернуть оригиналы на устройство и SHALL предупредить, если пользователь отказывается. Отвязка альбома и выключение -синхронизации SHALL NOT удалять фото с сервера. +синхронизации SHALL NOT удалять фото с сервера, а скачанные с сервера файлы SHALL +оставаться в галерее как обычные. Выключение синхронизации SHALL быть явным и +устойчивым: восстановление происхождения SHALL NOT возобновлять связь, пока +пользователь не включит синхронизацию снова. #### Scenario: Возврат оригиналов @@ -197,29 +198,44 @@ API, а изменение содержимого SHALL трактовать к #### Scenario: Отвязка - **WHEN** пользователь отвязывает альбом устройства от серверного -- **THEN** фото на сервере сохраняются +- **THEN** фото на сервере сохраняются, а скачанные файлы остаются в галерее как обычные #### Scenario: Выключение синхронизации - **WHEN** пользователь выключает синхронизацию альбома устройства - **THEN** связь с серверным альбомом снимается, а фото на сервере сохраняются +#### Scenario: Выключение не отменяется восстановлением + +- **WHEN** пользователь выключил синхронизацию (или отвязал альбом), и затем выполняется проверка с восстановлением происхождения по меткам превью +- **THEN** связь не восстанавливается, а альбом остаётся локальным с выбранным режимом выключено + +#### Scenario: Повторное включение + +- **WHEN** пользователь снова включает синхронизацию после выключения +- **THEN** связь создаётся заново, а восстановление происхождения снова действует по обычным правилам + ### Requirement: Автоматическая фоновая синхронизация -Синхронизация SHALL выполняться автоматически: при открытом приложении — по -событиям изменения, в фоне — периодически, с учётом ограничений сети, выбранных -пользователем. В режиме C разгрузка оригиналов SHALL выполняться автоматически -после успешной загрузки. +Синхронизация SHALL выполняться автоматически в обе стороны: при открытом приложении — +по событиям изменения, в фоне — периодически, с учётом ограничений сети, выбранных +пользователем. В режиме C разгрузка оригиналов SHALL выполняться автоматически после +того, как копия оказалась на сервере. #### Scenario: Фоновая синхронизация - **WHEN** приложение не открыто, а в альбоме устройства появились изменения - **THEN** синхронизация выполняется в фоне при ближайшей возможности +#### Scenario: Фоновая загрузка серверных фото + +- **WHEN** приложение не открыто, а в целевом серверном альбоме появилось новое фото +- **THEN** приложение материализует его на устройстве при ближайшей возможности + #### Scenario: Ограничение сети - **WHEN** включено ограничение «только Wi-Fi», а доступна только сотовая сеть -- **THEN** синхронизация не начинается до появления Wi-Fi +- **THEN** синхронизация, включая скачивание серверных фото, не начинается до появления Wi-Fi #### Scenario: Автоматическая разгрузка @@ -325,3 +341,139 @@ API, а изменение содержимого SHALL трактовать к - **WHEN** среди элементов альбома нет превью с серверной копией - **THEN** операция не запускается и ход не показывается + +### Requirement: Материализация серверных фото на устройстве + +При появлении фото в целевом серверном альбоме приложение SHALL материализовать его на +устройстве, записав файл в галерею (MediaStore) бакета альбома устройства: в режиме копий — +оригинал, в режиме переноса — превью, для видео — постер. Файл SHALL содержать метку +происхождения и дату съёмки серверного фото. + +#### Scenario: Материализация в режиме копий + +- **WHEN** в целевом серверном альбоме появилось новое фото, а альбом устройства в режиме копий +- **THEN** приложение скачивает оригинал и создаёт файл в бакете альбома устройства + +#### Scenario: Материализация в режиме переноса + +- **WHEN** в целевом серверном альбоме появилось новое фото, а альбом устройства в режиме переноса +- **THEN** приложение скачивает превью и создаёт файл в бакете альбома устройства + +#### Scenario: Материализация видео + +- **WHEN** в целевом серверном альбоме появилось видео +- **THEN** приложение создаёт в бакете постер-кадр, а оригинал остаётся на сервере + +#### Scenario: Дата съёмки + +- **WHEN** приложение материализует серверное фото +- **THEN** файл получает дату съёмки из серверных метаданных фото + +### Requirement: Признак происхождения элемента + +Каждый элемент альбома устройства SHALL иметь явный признак происхождения: загружено этим +клиентом или скачано с сервера. Политика удалений, разгрузка и восстановление SHALL +опираться на признак, а не на наличие серверной ссылки. + +#### Scenario: Скачанный элемент не считается своей загрузкой + +- **WHEN** элемент скачан с сервера при двусторонней синхронизации +- **THEN** он помечается как скачанный, а не как загруженный этим клиентом + +### Requirement: Двустороннее удаление элементов + +Удаление элемента в альбоме устройства SHALL удалять соответствующее фото на сервере +(мягко, в корзину), а серверное удаление SHALL убирать локальный элемент. Для +материализованного (серверного) элемента намерением считается только явное удаление в +приложении: пропавшая локальная копия SHALL NOT удалять серверное фото, а SHALL +материализоваться заново. Операция SHALL подчиняться правам сервера, а удаление файла, +снятого на устройстве, SHALL требовать системного согласия. + +#### Scenario: Удаление на устройстве + +- **WHEN** пользователь удаляет элемент синхронизированного альбома устройства +- **THEN** приложение помещает соответствующее серверное фото в корзину + +#### Scenario: Удаление на сервере + +- **WHEN** серверное фото помещено в корзину +- **THEN** приложение убирает соответствующий локальный элемент + +#### Scenario: Пропала локальная копия серверного фото + +- **WHEN** локальная копия материализованного серверного фото исчезла (очистка галереи, перемещение файла) +- **THEN** приложение материализует фото заново и SHALL NOT удалять его на сервере + +#### Scenario: Явное удаление в приложении + +- **WHEN** пользователь удаляет материализованное серверное фото из приложения +- **THEN** приложение помечает намерение и удаляет фото на сервере + +#### Scenario: Нет прав на серверное удаление + +- **WHEN** пользователь удаляет элемент, а роль на сервере не позволяет удалить это фото +- **THEN** серверное удаление отклоняется, приложение не считает операцию успешной и сообщает об этом + +### Requirement: Журнал удалений и защита от эха + +Локально удалённый элемент SHALL помечаться в журнале удалений, чтобы приложение SHALL NOT +материализовать его повторно до подтверждения серверного удаления. Повторные и эхо-события +удаления SHALL быть идемпотентны. + +#### Scenario: Удаление ещё не подтверждено сервером + +- **WHEN** элемент удалён на устройстве, а серверное удаление ещё не выполнено +- **THEN** при следующем проходе приложение не материализует этот элемент заново + +#### Scenario: Эхо серверного события + +- **WHEN** серверное событие удаления относится к уже удалённому локальному элементу +- **THEN** приложение ничего не меняет повторно + +### Requirement: Восстановление элемента из корзины + +Если серверное фото, удалённое с устройства, восстановлено из корзины сервера, приложение +SHALL снова материализовать соответствующий элемент на устройстве. + +#### Scenario: Фото вернулось из корзины + +- **WHEN** пользователь восстанавливает серверное фото из корзины +- **THEN** приложение материализует соответствующий элемент в альбоме устройства + +### Requirement: Первый проход и ограничение скачивания + +При включении синхронизации приложение SHALL предложить выбрать: скачать всё содержимое +целевого серверного альбома или только новое с этого момента. Скачивание SHALL подчиняться +ограничению «только Wi-Fi»; при ожидании подходящей сети материализация не начинается. + +#### Scenario: Только новое + +- **WHEN** пользователь выбирает «только новое» при включении синхронизации +- **THEN** уже существующее содержимое серверного альбома не скачивается, а материализуются только последующие появления + +#### Scenario: Скачать всё + +- **WHEN** пользователь выбирает «скачать всё» при включении синхронизации +- **THEN** приложение материализует весь текущий состав серверного альбома + +#### Scenario: Ожидание WiFi + +- **WHEN** включено ограничение «только Wi-Fi», а доступна только сотовая сеть +- **THEN** скачивание не начинается до появления Wi-Fi + +### Requirement: Материализация при нескольких источниках + +Когда несколько альбомов устройства указывают на один серверный альбом, приложение SHALL +материализовать серверное фото ровно в один из них, чтобы не создавать дубликат в галерее. +Выбор целевого бакета SHALL сохраняться. Содержимое, уже присутствующее на устройстве, +SHALL NOT дублироваться. + +#### Scenario: Несколько источников в одну цель + +- **WHEN** два альбома устройства синхронизируются с одним серверным альбомом, и в нём появляется фото +- **THEN** приложение создаёт файл ровно в одном из бакетов + +#### Scenario: Содержимое уже на устройстве + +- **WHEN** появившееся серверное фото по содержимому уже присутствует на устройстве +- **THEN** приложение связывает элемент с серверным фото, не создавая второй файл