Files
ds-galo/Отчёты об изменениях/2026-10-21-изменения-без-спецификаций.md
T

160 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Отчёт: изменения, сделанные без спецификаций
- **Дата:** 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'`).