docs: описание релиза Galo за 21 октября 2026
Описание изменений с main (84ce4d1) до develop (afe85f7): локальный режим на SQLite и файловой системе, слой объектов, редактирование фото, новый интерфейс рабочего пространства, видео, Android-клиент, профиль, офлайн, живое обновление, исправления и инструкция по обновлению.
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
# Описание релиза 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)`.
|
||||
Reference in New Issue
Block a user