160 lines
13 KiB
Markdown
160 lines
13 KiB
Markdown
# Отчёт: изменения, сделанные без спецификаций
|
||
|
||
- **Дата:** 2026-10-21
|
||
- **Диапазон:** `45a953f^..eb2d478` — 21 коммит, 2026-10-20 — 2026-10-21
|
||
- **Ветка:** `develop`
|
||
- **Предмет:** правки веб-интерфейса просмотра и нового инструмента «Свет» (редактирование фото), сделанные без обновления спецификаций OpenSpec
|
||
- **Результат:** спеки дописаны (`748143c`), заведено изменение `fix-copy-edit-variant-sharing`
|
||
|
||
## 1. Метод
|
||
|
||
1. Разобран диапазон коммитов `45a953f^..eb2d478`: для каждого проверено, затронул ли
|
||
он `openspec/specs`.
|
||
2. Для коммитов без спек — прочитаны затронутые участки кода
|
||
(`frontend/src/lib/components/Viewer.svelte`, `PhotoEditPanel.svelte`,
|
||
`PhotoPanel.svelte`, `frontend/src/lib/edit.ts`, `backend/internal/photos/worker.go`,
|
||
`store.go`, `sqlite_store.go`).
|
||
3. Сопоставлено фактическое поведение с текстом существующих требований
|
||
`ui-workspace`, `media-objects`, `photos`, `dev-make-workflow`.
|
||
4. Каждое найденное расхождение зафиксировано как требование (SHALL) со сценариями
|
||
WHEN/THEN; после правок спеки проверены `openspec validate --specs`.
|
||
|
||
## 2. Сводка
|
||
|
||
| Показатель | Значение |
|
||
| --- | --- |
|
||
| Коммитов в диапазоне | 21 |
|
||
| Из них со спеками | 11 |
|
||
| Из них без спек | 10 |
|
||
| Затронутых спецификаций | 4 (`ui-workspace`, `media-objects`, `photos`, `dev-make-workflow`) |
|
||
| Новых требований | 3 («График тональной кривой», «Навигация в зуме», «Сборка WASM-модуля фильтров») |
|
||
| Уточнённых требований | 6 |
|
||
| Новых сценариев | 12 |
|
||
| Ошибок валидации | 0 (`23 passed, 0 failed`) |
|
||
|
||
## 3. Покрытие по коммитам
|
||
|
||
| Коммит | Изменение | Где описано |
|
||
| --- | --- | --- |
|
||
| `45a953f` | скрытие боковых панелей, хоткеи размера миниатюр | `ui-workspace` → «Скрытие боковых панелей» (уже было) |
|
||
| `50b9983` | размер миниатюр не меняется при скрытых панелях | там же, сценарий «Размер миниатюр при скрытых панелях» |
|
||
| `35f8439` | зум клавишами `+` / `-` | `ui-workspace` → «Зум в просмотре» (уже было) |
|
||
| `0a9e527` | лента миниатюр следует за текущим фото | уже было |
|
||
| `961dff1` | `Ctrl+F` — полноэкранный режим приложения | уже было |
|
||
| `afaaaab` | офлайн-режим с кешем эскизов и превью | `ui-workspace` → «Офлайн-режим веб-приложения» (уже было) |
|
||
| `0548b1a` | вкладка «Редактирование», инструмент «Свет» | `ui-workspace` → «Вкладка «Редактирование»» (уточнено) |
|
||
| `cd3d17c` | правки применяются сразу; вкладка не сбрасывается при обновлении фото | уточнено: «Обновление фото SHALL сохранять открытую вкладку панели» |
|
||
| `6d3d7cb` | ускорение рендера правок в 9 раз | сознательно не специфицировано (внутренняя оптимизация) |
|
||
| `4d2069d`, `520886a` | клиентский рендер правок, WASM-фильтры | «Фильтры считаются в браузере» (уже было) |
|
||
| `2adcb13` | предпросмотр правок работает и без зума | уточнено: «Правки видны сразу — и во вписанном виде, и в зуме» |
|
||
| `500ec10`, `f8ee937` | починка зума, центрирование | «Зум в просмотре»: `Space` = 100% **от оригинала по центру кадра** |
|
||
| `b2a509f` | в зуме стрелки панорамируют фото | **новое** требование «Навигация в зуме» |
|
||
| `2668d87` | искажение цветов в предпросмотре | уже было (сценарий про сверку с эталоном) |
|
||
| `d904b2f` | невидимая линия тональной кривой | сознательно не специфицировано (багфикс) |
|
||
| `b64574b` | гистограммы каналов в редакторе кривой | **новое** требование «График тональной кривой» |
|
||
| `48ed0bb` | гистограмма пересчитывается при правке | там же, сценарий «Живая гистограмма» |
|
||
| `332f35b`, `eb2d478` | сворачивание блока «Кривая», свёрнут по умолчанию | там же, сценарий «Сворачивание кривой» |
|
||
| `520886a` | сборка WASM-модуля фильтров (`npm run wasm`, cargo) | `dev-make-workflow` → **новое** требование «Сборка WASM-модуля фильтров» |
|
||
| `520886a`, `worker.go` | роли объектов `edit`, `edit_thumb`, `edit_medium`, `edit_preview` | `media-objects` → «Журнал объектов» + сценарий «Привязка отредактированной версии» |
|
||
| `worker.go` | отдельная очередь рендера правок (`edit_jobs`) | `media-objects` → «Генерация вариантов» + «Рендер правок не блокирует варианты» |
|
||
| `worker.go` | рендер правок по исходному объекту | `photos` → «Неразрушающее редактирование фотографии» + «Рендер по оригиналу» |
|
||
|
||
## 4. Новые и уточнённые требования
|
||
|
||
**Новые требования:**
|
||
|
||
- `ui-workspace` → «График тональной кривой»: гистограмма каналов, живой пересчёт,
|
||
добавление и перетаскивание точек, сворачиваемый блок «Кривая» (по умолчанию свёрнут,
|
||
кнопки выбора канала остаются доступными).
|
||
- `ui-workspace` → «Навигация в зуме»: клавиши-стрелки панорамируют фото и не
|
||
переключают соседние снимки, переключение остаётся за мышью и кнопками у краёв.
|
||
- `dev-make-workflow` → «Сборка WASM-модуля фильтров»: Rust-исходники в
|
||
`frontend/wasm/light`, обновление модуля перед сборкой статики, откат на готовый
|
||
модуль из репозитория, если cargo недоступен.
|
||
|
||
**Уточнённые требования:**
|
||
|
||
- `ui-workspace` → «Вкладка «Редактирование»»: добавлено сохранение вкладки при
|
||
обновлении фото; предпросмотр правок работает в любом режиме просмотра.
|
||
- `ui-workspace` → «Зум в просмотре»: `Space` показывает 100% **от оригинала** по
|
||
центру кадра.
|
||
- `media-objects` → «Журнал объектов»: перечислены роли `edit`, `edit_thumb`,
|
||
`edit_medium`, `edit_preview`.
|
||
- `media-objects` → «Совместные объекты»: уточнено, что отредактированная версия
|
||
создаётся и привязывается только к фото, чьи правки её породили.
|
||
- `media-objects` → «Генерация вариантов»: рендер правок идёт в отдельной очереди.
|
||
- `photos` → «Неразрушающее редактирование фотографии»: рендер идёт по исходному
|
||
объекту, а не по уменьшенной копии.
|
||
|
||
## 5. Что сознательно осталось без спецификаций
|
||
|
||
| Изменение | Почему |
|
||
| --- | --- |
|
||
| `6d3d7cb` — ускорение рендера в 9 раз | внутренняя оптимизация, внешнее поведение не изменилось |
|
||
| `d904b2f` — видимость линии кривой | багфикс: требование «кривая показывается» уже было |
|
||
| `500ec10` — контейнер прокрутки зума | деталь вёрстки, покрыта требованием «Панорамирование» |
|
||
| правки переноса/центрирования внутри `f8ee937` | описаны пользовательские последствия, а не механизм |
|
||
|
||
## 6. Найденное расхождение кода и замысла
|
||
|
||
`Store.Clone` и `SQLiteStore.Clone` копируют **все** привязки `photo_objects`,
|
||
включая роли `edit*`, хотя воркер создаёт edit-объекты с владельцем
|
||
`[]string{photoID}` и комментирует это так: «The edit belongs to this photo only, so
|
||
the variants are not shared with the copies of the same original».
|
||
|
||
Последствия:
|
||
|
||
| Сценарий | Фактическое поведение | Ожидаемое |
|
||
| --- | --- | --- |
|
||
| Копия **до** правки | копия в исходном виде | так и должно быть |
|
||
| Копия **после** рендера | копия показывает отредактированную картинку, но `edit_params` пусты: в панели правок параметров нет, первая же новая правка перезаписывает обработку с нуля | копия в исходном виде |
|
||
|
||
Оформлено как изменение `fix-copy-edit-variant-sharing`
|
||
(`openspec/changes/fix-copy-edit-variant-sharing/`):
|
||
|
||
- `proposal.md` — зачем;
|
||
- `design.md` — фильтр ролей в `Clone` вместо правки выдачи URL, единый список
|
||
edit-ролей, разовая чистка существующих копий миграцией в обоих диалектах;
|
||
- `specs/media-objects/spec.md` — дельта требования «Совместные объекты»;
|
||
- `tasks.md` — 4 группы задач (список ролей → копирование → миграции → проверки).
|
||
|
||
## 7. Проверка
|
||
|
||
```
|
||
openspec validate --specs → 23 passed, 0 failed
|
||
openspec validate fix-copy-edit-variant-sharing --type change → valid
|
||
```
|
||
|
||
## 8. Реализация `fix-copy-edit-variant-sharing`
|
||
|
||
Изменение реализовано полностью (9/9 задач):
|
||
|
||
| Что | Где |
|
||
| --- | --- |
|
||
| Общий список edit-ролей `EditRoles()` и предикат `IsEditRole()` | `backend/internal/photos/edit_render.go` |
|
||
| `Clone` не переносит edit-привязки (Postgres: `role <> ALL($3::text[])`) | `backend/internal/photos/store.go` |
|
||
| То же для SQLite (`role NOT IN (...)`) | `backend/internal/photos/sqlite_store.go` |
|
||
| Сброс правок использует общий список ролей | `backend/internal/photos/edit_service.go` |
|
||
| Разовая чистка копий, созданных до фикса | `backend/migrations/000016_*.sql`, `backend/migrations/sqlite/000008_*.sql` |
|
||
| Контрактная проверка копирования (оба хранилища) | `backend/internal/repo/contract/contract.go`, кейс `Clone` |
|
||
| Сервисные тесты: копия и миграция | `backend/internal/photos/copy_test.go`, `backend/internal/e2e/copy_edit_test.go` |
|
||
|
||
Проверки:
|
||
|
||
- `make test` (с Postgres) — весь бэкенд зелёный;
|
||
- контрактный кейс `Clone` проходит на SQLite и Postgres; без фикса он падает
|
||
(«clone shares the edited version»);
|
||
- e2e на SQLite: копия показывает исходник, `edit_params` пусты, миграция убирает
|
||
«чужие» привязки, не трогая привязки владельца;
|
||
- ручная проверка через HTTP и браузер: в просмотре копии грузится `thumb.webp`
|
||
(не `edit_preview`), все ползунки панели «Редактирование» в нуле.
|
||
|
||
## 9. Что дальше
|
||
|
||
- Ревью и архивация изменения `/opsx-archive` — дельта `media-objects` переедет в
|
||
основную спеку.
|
||
- Изменение требует Postgres для полного прогона: в этом окружении поднимался
|
||
временный контейнер `postgres:16-alpine` на порту 55432
|
||
(`TEST_DATABASE_URL='postgres://postgres:postgres@127.0.0.1:55432/galo?sslmode=disable'`).
|
||
|