В режиме просмотра альбома добавлена кнопка скрытия левой и правой боковых
панелей (рядом с регулятором размера миниатюр) и хоткей Tab. В скрытом режиме
панели не занимают место в раскладке, а появляются по наведению на левую или
правую кромку окна и прячутся после ухода указателя.
Клавиши + и - меняют размер миниатюр на шаг, как кнопки регулятора; хоткей
работает на всех экранах приложения и не срабатывает при вводе текста, в
открытых диалогах и меню.
Спека ui-workspace дополнена требованиями о скрытии панелей и клавишах
изменения размера миниатюр.
В спеку phone-album-sync добавлены требования о роли локального файла,
подтверждении новой версии до удаления устаревшей и разгрузке без
незавершённой загрузки; уточнены синхронизация изменений, провенанс и
возврат оригиналов.
Изменение описывает три дефекта двусторонней синхронизации: повторную
загрузку собственного превью, удаление серверной копии до подтверждения
замены и повторную разгрузку только что восстановленного оригинала.
Новое изменение: двусторонний миррор альбома устройства и серверного альбома
(материализация серверных фото, признак происхождения, двусторонние удаления с
явным намерением, устойчивое выключение, первый проход, n:1). Purpose основной
спеки phone-album-sync обновлён под двустороннюю синхронизацию.
Change проходит `openspec validate --strict`.
Дельта перенесена в основные спеки: android-client («Прогресс восстановления
оригиналов») и phone-album-sync («Ход и устойчивость восстановления оригиналов»).
Изменение перемещено в openspec/changes/archive/2026-10-16-android-album-restore-progress.
Возврат из переноса в копии скачивает оригиналы с сервера, но ход нигде не
отражался: карточка умела показывать только загрузку, а восстановление шло в
области экрана и обрывалось при уходе с него.
Добавлен RestoreCoordinator: выполняет восстановление в области приложения
(уход с экрана больше не прерывает), ведёт ход «альбом -> восстановлено из
всего» и защищает от одновременного запуска по одному альбому. RestoreManager
сообщает ход по каждому элементу и вынесен за интерфейс AlbumRestorer; SyncCheck
ходит через координатора и, как и раньше, дожидается завершения.
Карточка альбома показывает «Копии — восстановление N%» (пульсирует), экран
альбома устройства — строку «Восстановление оригиналов: X из Y» с полосой.
Изменение OpenSpec: android-album-restore-progress.
Карточка альбома устройства получает третью строку: метку режима («Копии»/
«Перенос») и ход загрузки. Процент считается как (uploaded + running) / total
для обоих синхронизируемых режимов, пульсирует только при активной загрузке,
показывает очередь, ожидание Wi-Fi и ошибку. В режиме переноса карточка
показывает и карту памяти, и облако — режимы различает метка, а не значки.
Разгрузка оригиналов теперь требует подтверждённой серверной копии в текущем
целевом альбоме: устаревший serverPhotoId перепроверяется по альбому, смена
цели переносит уже загруженные фото и отклоняется, если перенести нельзя.
Потеря серверной копии видна на карточке и на экране альбома, автозатирания
превью нет.
Изменения OpenSpec: android-album-card-sync-status, safe-album-offload.
Новое требование в phone-album-sync о потере/возврате серверной копии у
разгруженного превью, требование в android-client о метке потерянного элемента
и действии удаления с устройства.
- realtime: личный поток несёт события жизненного цикла альбома и изменения
счётчика фото; добавлены требования о жизненном цикле, получателях, форме
payload'а, догоне и потере доступа
- albums: photos_count считает только фото вне корзины
Album panel and photo sheet with attributes/actions for every album (server and
device), local album rename/cover/delete, EXIF attribute settings, and a unified
device-gallery sync check:
- add repository "Атрибуты и действия" panel (swipe left) on server and device
albums: cover, info, navigation, rename/delete; album settings screen; photo
sheet (swipe up) with info, tags and actions; EXIF attribute settings backed by
profile preferences
- local albums use local metadata and actions: app-only display name and cover
(new device_album_meta table), delete removes photos from the phone via the
system delete consent
- unified mode x state policy (GallerySyncPolicy/SyncCheck) runs on start and on
album open: upload/offload in move, restore originals in copy/off
- fix move->copy restore, offload of any linked-but-local original, provenance
recovery that no longer overwrites the album mode, serialized reconcile and
duplicate-upload suppression; prompt for "all files" access on start
- album card caption: name (with linked server name when different) and storage
icons (memory card / cloud) with item count
Sync delta specs into android-client and archive the change
add-mobile-album-photo-actions.
Verified: unit/Robolectric suite green, :app:assembleDebug, :app:lint, and the
DeviceSyncE2ETest instrumented suite on the emulator (7/7).
Show a spinner in the top-right of the photo viewer while the original is
being loaded on zoom, so the wait is visible. Only the original triggers it;
the fitted preview and a re-shown preview never do.
Cover the visibility gate with a unit test.
OpenSpec change: add-mobile-photo-zoom
Add pinch/pan/double-tap zoom to the full-screen photo viewer, mirroring the
web viewer semantics:
- scale is a multiplier of the natural size, capped at 200%; the fitted view
is the lower bound
- double tap toggles fitted view <-> 100% natural anchored at the tap
- the original loads on zoom, with the preview kept as a placeholder so
entering zoom never flashes a black surface
- the pager is disabled while zoomed (drags pan the image) and the zoom resets
when leaving a photo
- video and 360 panoramas are unaffected
Implement PhotoZoom (zoom math) and ZoomableImage (gestures), wire it into
ViewerContent/MediaPage, and cover it with unit, Compose and device tests.
OpenSpec change: add-mobile-photo-zoom
- add `help` target and make it the default goal (bare `make` lists targets)
- add `install-android-app`: build debug APK and install on the selected device
- device selection via SERIAL/$ANDROID_SERIAL; several devices list and stop
- document the target in android/README.md
Reload the rail trash count after every operation that changes the
trash contents, not just navigation and empty-trash: moving photos to
trash (grid, panel, viewer), restoring, permanent delete and deleting
an album. Previously the counter stayed stale until the next route
change.
Kotlin + Jetpack Compose client for a self-hosted instance: manual
server setup and health check, auth with Keystore-backed tokens, album
and photo browsing with SSE live updates, upload queue in WorkManager
with HEIC-to-JPEG conversion, and Room/Coil offline cache. Add an
android Makefile target and revise project.md §17.
Add a shared photo-updates signal the panel publishes on rename or tag
add/remove; the album grid and viewer subscribe and patch the changed
photo into their lists, so the 360 badge and panorama mode update without
a reload.
Build signed object URLs as path-only (/api/objects/...) instead of
prefixing APP_BASE_URL, so media loads from whatever host or reverse
proxy served the app. Remove the now-unused base URL from the signer and
drop the APP_BASE_URL / Server.BaseURL config.
The panorama viewer inverted horizontal drag relative to vertical: the
yaw sign was opposite, so dragging right turned the view right instead
of moving the scene right. Flip yaw to match the pitch convention
(drag the world) and lock the behavior with a ui-workspace scenario.
Permanently deleting a photo whose album was deleted failed because
authorization resolved through the album. Fall back to uploader
ownership when the photo is albumless. Add a bulk DELETE /trash that
removes every trashed photo the caller may delete (uncapped listing)
and returns the deleted count, with an «Очистить корзину» action on the
trash screen.
Add video as a media kind in photos: streaming upload with per-kind
limits, ffprobe metadata, poster frame reusing the image variant
pipeline, and a playback object (remux when web-compatible, else
h264/yuv420p/aac transcode). Video jobs run on a separate pool with a
long lease. Frontend gains a video viewer, grid badge/duration and
video upload.
- Touch/pen tap opens the viewer; mouse click still selects, dblclick opens
- Panel and rail emit a single transform utility so the panel actually
slides in below 1280px (Tailwind v4 utility ordering made the base
translate-x-full win over the conditional translate-x-0)
- Add touch-action: manipulation to grid cells
- Detect 'pano360' tag and open the photo in a spherical viewer
- Mini-WebGL equirect viewer (no deps): drag to look, wheel FOV, Space reset
- Load original as origin-clean bitmap; fall back to flat image without WebGL
- 360 indicator in viewer and badge on grid tiles