feat(photos): добавить пресеты кривой и починить переключение фото

- PCHIP для предпросмотра и серверного рендера
- пресеты всех каналов в профиле, импорт и экспорт
- мерж настроек auth без потери разделов
- сохранение вкладки и правок текущего фото при переключении
This commit is contained in:
iu
2026-10-23 22:56:55 +03:00
parent 3b8406fb4c
commit 0bfee5f2f8
20 changed files with 2127 additions and 105 deletions
@@ -0,0 +1,73 @@
# Proposal
## Why
Тоновая кривая сейчас ведёт себя не как инструмент из Lightroom: крайние узлы не
существуют до первых кликов, линия строится по прямым сегментам, а готовых
пресетов контраста и пользовательских пресетов нет. Пользователь не может быстро
применить типовой контраст или сохранить свою кривую. Дополнительно
`PATCH /auth/me/preferences` заменяет весь объект настроек, поэтому хранение
пресетов в профиле без мержа затирало бы другие разделы.
## What Changes
- **Кривая.** Крайние узлы `(0,0)` и `(1,1)` существуют сразу и являются обычными
точками; первый клик добавляет точку между ними. Крайние узлы двигаются по x и
y, вне них кривая постоянна («полки», как на скрине). Линия и рендер используют
плавную монотонную кубическую интерполяцию (PCHIP): SVG-путь и LUT в Go/TS.
«Сбросить» возвращает линейную кривую.
- **Встроенные пресеты.** `Линейный`, `Средний контраст`, `Сильный контраст` —
каждый описывает все четыре канала: контраст задаётся кривой RGB, каналы
R/G/B остаются линейными. Подпись показывает активный пресет; при ручной
правке любого канала — `Пользовательский`.
- **Пользовательские пресеты.** Пресет описывает кривые всех каналов
(`name`, `channels: { rgb, red, green, blue }`). Сохранение текущих кривых
всех каналов, применение заменяет все четыре кривые, переименование и
удаление. Хранение в `users.preferences.curve_presets` (профиль пользователя),
приватно для пользователя.
- **Мерж настроек.** `PATCH /auth/me/preferences` принимает `photo_info` и
`curve_presets` по отдельности, мержит переданные разделы с сохранёнными,
валидирует и возвращает полный merged-объект. Сохранение «Сведений» больше не
затирает пресеты, и наоборот.
- **Импорт/экспорт.** JSON-файл `{ version: 1, presets: [...] }`: экспорт
пользовательских пресетов, импорт с добавлением в профиль (при совпадении имени
— перезапись).
- **Совместимость.** Миграций схемы нет: пресеты лежат в JSON-блобе
`preferences`. Старые пресеты формата `{ channel, points }` конвертируются в
четыре канала при чтении и перезаписываются при следующем сохранении.
Существующие сохранённые кривые начнут интерполироваться плавно, поэтому их
визуальный результат может немного измениться.
## Capabilities
### New Capabilities
Нет — поведение входит в существующие способности `photos` и `auth`.
### Modified Capabilities
- `photos`: добавляются требования «Тоновая кривая» (крайние узлы, плавная
интерполяция, встроенные пресеты, сброс) и «Пользовательские пресеты кривой»
(пресет из четырёх каналов, сохранение/применение/переименование/удаление,
импорт/экспорт).
- `auth`: требование «Хранение пользовательских настроек интерфейса»
расширяется мержем разделов и хранением пользовательских пресетов кривой;
добавляется сценарий «сохранение одного раздела не затирает другие».
## Impact
- `backend/internal/auth/handlers.go` — `preferencesPayload` с `curve_presets`,
мерж разделов, валидация имени и кривых всех четырёх каналов.
- `backend/internal/auth/store.go` / `sqlite_store.go` — без изменений схемы;
запись merged-JSON через существующий `UpdatePreferences`.
- `backend/internal/photos/edit.go` — `curveEval`/`curveLUT` переходят на PCHIP;
тесты Go на гладкость/LUT.
- `frontend/src/lib/edit.ts` — PCHIP в `buildCurveLUT`, типы и нормализация
пресетов, точки кривой.
- `frontend/src/lib/components/PhotoEditPanel.svelte` — узлы-углы, плавный SVG-путь,
меню пресетов, сохранение/переименование/удаление, импорт/экспорт.
- `frontend/src/lib/types.ts`, `stores/auth.svelte.ts`, `api.ts` — тип
`Preferences.curve_presets`, сохранение через существующий endpoint.
- Новые вспомогательные модули: меню/диалоги пресетов, импорт/экспорт JSON.
- Тесты: `edit_test.go`, `edit.test.ts`, тесты мержа/валидации preferences,
e2e сохранения пресетов в профиле.