# Описание релиза Galo — 21 октября 2026 **База релиза:** `main` (`84ce4d1`, 10 октября 2026) → `develop` (`afe85f7`, 21 октября 2026) **Объём:** 138 коммитов, 4 спецификации обновлены, 1 изменение реализовано --- ## Коротко о главном 1. **Локальный режим без внешних сервисов.** Слой метаданных переключается на встроенный SQLite, хранилище блобов — на локальную файловую систему. По умолчанию приложение работает полностью на одной машине: без Postgres и без S3. 2. **Редактирование фотографий в браузере.** Вкладка «Редактирование» с инструментом «Свет»: экспозиция, контраст, света, тени, белые и чёрные, тональная кривая по каналам с живой гистограммой. Правки видны сразу, без ожидания сервера. 3. **Новый интерфейс рабочего пространства.** Альбомы с обложками, тёмная, светлая и нейтральная темы, единая система векторных иконок, гало-эмблема и фавикон. 4. **Нативный Android-клиент.** Отдельное приложение: просмотр альбомов, загрузка с устройства, двусторонняя синхронизация с сервером, безопасная разгрузка превью. 5. **Видео и офлайн.** Видео в общих альбомах с фоновой подготовкой и перемоткой; офлайн-кеш оболочки, списков и превью. --- ## Новые возможности ### Локальный режим: SQLite и файловая система - **Слой метаданных** выбирается конфигурацией: внешний PostgreSQL или встроенный SQLite с тем же поведением для одного экземпляра приложения. - **Хранилище блобов** выбирается отдельно: S3-совместимое (Garage) или локальная файловая система. Клиент никогда не видит хранилище — блобы всегда отдаются через бэкенд по подписанному URL. - По умолчанию (`DB_BACKEND=sqlite`, `STORAGE_BACKEND=fs`) приложение поднимается без Postgres и S3: данные лежат в `galo.db` и каталоге `data/`. - **Секрет подписи JWT и URL объектов** генерируются при первом старте и хранятся в слое метаданных — переживают перезапуск и рестарт SQLite. - Реконсиляция хранилища с журналом объектов: `make reconcile` (отчёт), `make reconcile-delete` (удаление осиротевших блобов). Сборку мусора ведёт журнал, хранилище не сканируется. ### Слой объектов - Физическое объектное хранилище с **непрозрачными ключами**: идентификаторы альбомов и фото в ключах не участвуют, сопоставление живёт только в метаданных. - **Журнал объектов**: у каждого объекта ровно одна запись с ключом, MIME-типом, размером, габаритами и состоянием; фото ссылается на объекты по ролям (`original`, `thumb`, `medium`, `preview`, `playback`, `edit`, `edit_thumb`, `edit_medium`, `edit_preview`). - Один физический объект может разделяться несколькими фото: перенос и копирование меняют только ссылки, хранилище не трогается. - Сборка мусора по ссылкам: объект удаляется, когда на него не ссылается ни одно фото и ни один аватар. ### Редактирование фотографий - Вкладка «Редактирование» в правой панели фото, инструмент «Свет»: экспозиция, контраст, света, тени, белые, чёрные. - **Тональная кривая**: выбор канала (R/G/B/мастер), точки добавляются кликом по полю графика, перетаскиваются и двигаются стрелками с клавиатуры. - **Гистограмма каналов** пересчитывается на лету с учётом текущих правок. - Блок «Кривая» сворачивается (по умолчанию свёрнут), кнопки выбора канала остаются доступными. - Предпросмотр правок рисуется **в браузере** — WASM-модулем на Rust, с резервной JS-реализацией, если модуль недоступен или считает иначе. - Правки применяются сразу; на сервер уходят через 10 секунд без изменений или при переключении фото. Сервер рендерит отредактированную версию в фоне отдельной очередью и сообщает о готовности. - Переключатель «Сброс» удаляет и параметры правок, и отредактированную версию. - Редактирование неразрушающее: оригинал остаётся неизменным, скачивание отдаёт исходник. Рендер идёт по исходному объекту, а не по уменьшенной копии. ### Интерфейс рабочего пространства - Альбомы с обложками, «лайтрумовская» компоновка: рельс альбомов, сетка, единая панель фотографии. - Три темы — тёмная, светлая, нейтральная — с семантическими токенами; переключатель тем переехал в шапку, нейтральная тема затемнена. - **Единая система иконок**: встроенная векторная графика (Material Symbols) вместо эмодзи и символов системного шрифта — иконки наследуют цвет темы и не тянут внешние ресурсы. - **Гало-эмблема** (диск солнца в отделённом кольце) и фирменный фавикон: отдаются самим приложением, наследуют тему, читаются в размере 16–24 px. - Панель сведений о фото со **сворачиваемыми секциями** и настраиваемым набором атрибутов: состав EXIF хранится в профиле пользователя. - Полноэкранный просмотр: своя панель, кнопка «на весь экран», возврат по `Esc`. ### Просмотр и навигация - **Зум** колесом мыши (с привязкой к курсору, до 200%), клавишами `+` и `-`; `Space` возвращает во вписанный вид, а из вписанного — в 100% от оригинала по центру кадра. - В зуме стрелки клавиатуры **панорамируют** фото, переключение соседних снимков остаётся за мышью и кнопками у краёв. - **Скрытие боковых панелей** альбома: кнопка в панели сетки и хоткей `Tab`. Панели уезжают за края, содержимое занимает всю ширину, панели возвращаются при наведении на кромку окна. Размер миниатюр при этом сохраняется — в строке помещается больше фото. - Лента миниатюр в просмотре следует за текущим фото; на превью работает наведение- зум. - Полноэкранный режим приложения по `Ctrl+F`, навигация с клавиатуры в сетке с возвратом фокуса, открытие фото тапом на тач-экранах. - Просмотр 360-панорам по тегу `pano360`, корректное направление перетаскивания. - Пока грузится оригинал для зума — спиннер; на Android — щипок-зум. ### Работа с фотографиями - Мультивыбор в сетке, контекстные меню, **перенос и копирование** снимков, корзина на уровне альбома и общие счётчики. - **Теги** фото: правки расходятся по сетке и просмотру. - Переименование фото и удаление из просмотра с подтверждением. - EXIF извлекается полностью при загрузке; для старых снимков — дозаполнение (`cmd/backfill-exif`). ### Офлайн-режим - Service worker кеширует оболочку приложения, списки и эскизы/превью фотографий: открытые альбомы и ранее загруженные снимки доступны без сети. - Модуль фильтров в офлайне не кешируется — он всегда подтягивается свежим. ### Видео - Загрузка видео в те же альбомы, что и фото (`kind=video`). - Фоновая подготовка: постер (кадр кадра → обычные варианты сетки) и web-совместимый вариант для воспроизведения (ремукс без перекодирования либо транскод в h264/yuv420p/aac с `+faststart`), с поддержкой перемотки (`Range`). - Транскод идёт в отдельном малом пуле и не вытесняет генерацию вариантов изображений; готовность транслируется подписчикам альбома. - Без `ffmpeg`/`ffprobe` приём видео отклоняется, `/api/health` сообщает `"video": "unavailable"`, изображения работают как прежде. ### Android-клиент - Отдельный Gradle-модуль, работает с существующим HTTP API, сервер и веб не трогает. Kotlin + Jetpack Compose, `minSdk 28`, `targetSdk 35`. - **Двусторонняя синхронизация** альбома устройства с серверным альбомом: загрузки, изменения состава, удаления. - **Безопасная разгрузка**: с устройства уходит превью, оригинал остаётся на сервере; потеря серверной копии распознаётся и показывается в интерфейсе. - Статус синхронизации на карточке альбома, ход восстановления оригиналов, сортировка списка, обновление списка альбомов в реальном времени. - Установка на устройство одной командой: `make install-android-app` (с выбором серийного номера при нескольких подключённых устройствах). ### Профиль и учётная запись - Профиль пользователя: круглый аватар с загрузкой и кадрированием, публичная отдача аватара по версионированной ссылке. - Вход и регистрация по телефону (E.164) в дополнение к email, управление идентификаторами, удаление аккаунта. - Предпочтения пользователя (в том числе состав панели сведений) хранятся на сервере. ### Альбомы и живое обновление - Обложки альбомов: отдельный жизненный цикл, обложка доводится до готовности и доставляется подписчикам. - Живое обновление: события жизненного цикла альбома и изменения состава фото через SSE, подписчики видят изменения без перезагрузки. - Квадратный вариант `medium` — резкие миниатюры в сетке; шаг переключения размера миниатюр и направление ползунка приведены в порядок. - Быстрая загрузка больших альбомов. ### Наблюдаемость и эксплуатация - Настраиваемое логирование: `LOG_LEVEL` (`debug` включает access-логи и ожидаемые 4xx), `LOG_FORMAT` (`json`/`text`), request id в ответах и логах. - `scripts/seed.py` и `make testdata` — наполнение запущенного инстанса тестовыми данными через публичный API. - Release-сборка: `make build-release` — статичный бинарник с вшитой версией и UPX-сжатием, если он установлен. --- ## Исправления - **Копия фото больше не наследует отредактированную версию.** Раньше копирование переносило все привязки объектов, включая `edit*`: копия показывала отредактированную картинку, но не наследовала параметры правок, и первая же новая правка перезаписывала обработку с нуля. Теперь правка принадлежит своему фото, а копия показывает исходник. Разовая миграция убирает привязки у копий, созданных до фикса. - Предпросмотр правок работает и без зума, а не только в увеличенном виде. - Устранено искажение цветов в предпросмотре (WASM-фильтры считались по преобразованным каналам). - Гистограмма пересчитывается при правке, а не остаётся от исходного снимка. - Линия тональной кривой видима в тёмной теме. - Починен зум в просмотре: контейнер прокрутки и центрирование. - Android: разгруженное превью больше не считается новой версией элемента. - Сервер отдаёт контрольную сумму оригинала в ответе на загрузку — клиент может сверить файл. - Счётчик корзины и правки тегов синхронизируются с сеткой и просмотром. - Ошибка загрузки крупного файла показывается явно, лимит поднят. - `make run` пересобирает фронтенд, если он изменился. --- ## Технические изменения ### API Новые эндпоинты: - `PATCH /api/photos/:id/edit` — сохранить параметры правок (тело: `{"light": {...}}`). - `DELETE /api/photos/:id/edit` — сбросить правки вместе с отредактированной версией. - `GET|PATCH|DELETE /api/auth/me`, `POST /api/auth/me/password`, `POST /api/auth/me/identifiers`, `PATCH /api/auth/me/preferences`, `POST|DELETE /api/auth/me/avatar`, `GET /api/avatars/:userID/:version?size=large`. - `POST /api/photo-batch/copy`, `POST /api/photo-batch/move` — пакетные операции над снимками. Изменившиеся ответы: у фото появились поля `edit_params`, `edit_url`, `edit_thumb_url`, `edit_medium_url`, `edit_preview_url`; у видео — `playback_url`. ### Схема данных и миграции - Postgres: миграции до `000016`; SQLite: до `000008` (нумерация диалектов независимая, миграции вшиты в бинарник и применяются автоматически при старте; `make migrate` применяет только Postgres). - Новые роли объектов `edit`, `edit_thumb`, `edit_medium`, `edit_preview` — рядом с обычными `original`, `thumb`, `medium`, `preview`, `playback`. - Новая колонка `photos.edit_params` — параметры неразрушающих правок. ### Сборка - Фильтры правок считает WASM-модуль из `frontend/wasm/light` (Rust, `no_std`). Пересборка нужна только при правке самого модуля; в репозитории лежит готовый `light.wasm`, поэтому фронтенд собирается без Rust-инструментов. - `npm run wasm` — отдельная пересборка модуля; обычная сборка фронтенда обновляет его сама. --- ## Как обновиться 1. Собрать бинарник: `make build` (фронтенд → `backend/static` → `go:embed` → `bin/galo`) или `make build-release` — с вшитой версией. 2. Остановить сервис, заменить бинарник. Фронтенк вшит в бинарник, отдельно выкладывать нечего. 3. Запустить: миграции применятся автоматически при старте для выбранного бэкенда. Для Postgres можно отдельно прогнать `make migrate`. 4. Для видео нужен `ffmpeg`/`ffprobe` на хосте; без них приём видео отключается. 5. Android-клиент ставится отдельно: `make install-android-app`. Откат: бинарник возвращается предыдущей версии; миграции `down` не восстанавливают удалённые привязки копий — это безопасно, отредактированные версии исходных фото остаются на месте. --- ## Ограничения - Редактирование — только для изображений; видео не редактируется. - Правки не порождают отдельной записи в истории: параметры хранятся в метаданных фото, отредактированная версия — как отдельный объект. - Офлайн-кеш ограничен оболочкой, списками и эскизами/превью: оригиналы для зума и скачивания требуют сети. - Android-клиент требует JDK 17 и Android SDK (`platforms;android-35`, `build-tools;35.0.0`). - Номер версии берётся из `git describe --tags`; в репозитории пока нет тегов, поэтому версия в сборке — короткий SHA или `dev`. Для осмысленной нумерации нужен тег. --- ## Для разработчиков - Спецификации OpenSpec синхронизированы с поведением: обновлены `ui-workspace` (редактирование, кривая, зум и навигация), `media-objects` (роли `edit*`, раздельное владение правками, отдельная очередь рендера), `photos` (рендер по оригиналу), `dev-make-workflow` (сборка WASM-модуля). - Изменение `fix-copy-edit-variant-sharing` реализовано (9/9 задач) и ждёт ревью и архивации: `openspec list` показывает его как `complete`. - Контрактные тесты репозиториев (`internal/repo/contract`) гоняют один набор проверок на SQLite и Postgres — новый кейс `Clone` проверяет, что копия не наследует отредактированную версию, в обоих хранилищах. - Интеграционные тесты Postgres требуют `TEST_DATABASE_URL`; без них они пропускаются. Тесты e2e поднимают SQLite в `t.TempDir()` и внешних сервисов не требуют. - Сервис переименован в `galo`, бренд — «гало»; сборка `make build-release` пишет `bin/galo-$(VERSION)`.