- backend: switch EXIF parser to dsoprea/go-exif/v3 (all recognised tags, numeric normalization); store in photos.exif jsonb (migration 000003); add storage.Get and cmd/backfill-exif to backfill existing photos from S3 originals - frontend: info panel renders all EXIF attributes with RU labels, enum translation and units (1/N, f/N, mm, EV); service tags hidden - viewer: remove make-cover action (cover managed from panel only), icon-only download - openspec: sync lightroom-workspace-ui artifacts (EXIF, cover-from-panel) and add plan for configurable info attributes/labels stored in user profile
69 lines
13 KiB
Markdown
69 lines
13 KiB
Markdown
# Tasks
|
||
|
||
## 1. Backend: обложка и целостность
|
||
|
||
- [x] 1.1 Добавить миграцию `000002_album_cover_fk` (up/down): обнулить висячие `cover_photo_id`, затем `ADD CONSTRAINT ... FOREIGN KEY (cover_photo_id) REFERENCES photos(id) ON DELETE SET NULL`. Проверка: `make migrate` применяется, `\d albums` показывает constraint, down-миграция снимает его.
|
||
- [x] 1.2 Добавить `CoverThumbURL` в `models.Album` (json `cover_thumb_url,omitempty`). Проверка: `cd backend && go build ./...` проходит.
|
||
- [x] 1.3 В `albums/store.go` добавить `LEFT JOIN photos p ON p.id = a.cover_photo_id` и выбирать `p.thumb_key AS cover_thumb_key` в `ListForUser` и `GetByID`. Проверка: запрос возвращает `cover_thumb_key` для альбома с обложкой и `NULL` без неё.
|
||
- [x] 1.4 В `albums/service.go` пресигнить `cover_thumb_key` в `CoverThumbURL` в `List` и `Get`. Проверка: `GET /api/albums` в ответе содержит `cover_thumb_url` у альбома с обложкой.
|
||
- [x] 1.5 В `photos/service.go` вызывать `SetCover` только при `cover_photo_id IS NULL`. Проверка: загрузка двух фото в новый альбом оставляет обложкой первое; после удаления фото-обложки cover становится `NULL`, следующая загрузка ставит новый.
|
||
- [x] 1.6 Покрыть тестами store-join обложки и автообложку; проверить `cd backend && go test ./...`.
|
||
|
||
## 2. Frontend: темы и каркас
|
||
|
||
- [x] 2.1 Добавить токены `--chrome`/`--canvas`, перенастроить `dark` в графит и сделать его дефолтом (при отсутствии сохранённого выбора). Проверка: `cd frontend && npm run check` проходит, все три темы визуально согласованы.
|
||
- [x] 2.2 Добавить `cover_thumb_url` в `lib/types.ts` и недостающие иконки (обложка, плотность/меню) в `lib/icons/registry.ts`. Проверка: `npm run check` без ошибок типов.
|
||
- [x] 2.3 Разбить `+layout.svelte` на группы `(app)`/`(public)` и ввести `AppShell` (топбар, слот рейла, слот панели, центр = маршрут). Проверка: навигация между альбомами не перезагружает рейл; `/login`, `/invites/[token]`, `/share/[token]` рендерятся без каркаса.
|
||
- [x] 2.4 Реализовать адаптивный каркас: рейл → drawer, панель → сворачиваемая секция, лента → горизонтальный скролл на узкой ширине. Проверка: при ширине меньше порога каркас управляем без горизонтального overflow.
|
||
|
||
## 3. Рейл и создание альбома
|
||
|
||
- [x] 3.1 Компонент `AlbumRail`: список альбомов с обложками, плейсхолдер, подсветка активного. Проверка: активный альбом выделен, остальные показывают обложку/плейсхолдер.
|
||
- [x] 3.2 Кнопка создания `+` с хинтом и модалка ввода названия (замена инлайн-формы). Проверка: создание альбома через модалку, новый альбом становится активным; пустое название не создаёт альбом.
|
||
|
||
## 4. Контекстная панель и обложки
|
||
|
||
- [x] 4.1 Компонент `AlbumPanel`: превью обложки, название, счётчики, переходы к участникам/настройкам/ссылкам; управление обложкой только для owner/admin. Проверка: viewer не видит управление обложкой.
|
||
- [x] 4.2 Модальный `CoverPicker` (сетка миниатюр) + `PATCH /albums/:id` с `cover_photo_id`/`clear_cover`. Проверка: назначение и сброс обложки отражаются в рейле, карточке и панели.
|
||
- [x] 4.3 Выбор обложки выполняется только из правой панели (модальный пикер); действие «Сделать обложкой» в тулбаре просмотра не используется. Проверка: обложка задаётся и сбрасывается из панели, в тулбаре просмотра кнопки нет.
|
||
|
||
## 5. Центр: сетка, плотность, загрузка
|
||
|
||
- [x] 5.1 Сетка альбомов edge-to-edge с реальными обложками вместо карточек-заглушек. Проверка: карточки показывают `cover_thumb_url`, при отсутствии — плейсхолдер.
|
||
- [x] 5.2 Регулятор плотности миниатюр в тулбаре сетки с сохранением в `localStorage`. Проверка: размер меняется и восстанавливается после перезагрузки.
|
||
- [x] 5.3 Загрузка: иконочная кнопка в тулбаре, полноэкранный drag-overlay, зона-подсказка в пустом альбоме; прогресс компактной полосой. Проверка: перетаскивание и выбор файлов загружают фото, пустой альбом показывает подсказку.
|
||
|
||
## 6. Просмотр как маршрут
|
||
|
||
- [x] 6.1 Маршрут `albums/[id]/photos/[photoId]` и компонент `Viewer` (крупное фото, filmstrip, панель метаданных/комментариев, тулбар, клавиши ←/→/Esc). Проверка: переход из сетки открывает маршрут, адрес отражает фото, навигация синхронна с filmstrip.
|
||
- [x] 6.2 Переиспользовать `Viewer` на `share/[token]` вместо инлайн-лайтбокса. Проверка: публичный просмотр использует тот же компонент без рейла/панели.
|
||
|
||
## 7. Интеграция
|
||
|
||
- [x] 7.1 Пройти сценарий: создать альбом → загрузить фото → задать обложку → открыть просмотр → открыть share-ссылку; проверить поведение на десктопе и узком экране.
|
||
- [x] 7.2 Проверить `make build` (сборка фронтенда в `backend/static`, статический Go-бинарник) и `openspec validate "lightroom-workspace-ui"`.
|
||
|
||
## 8. Дополнительно реализовано (после планирования)
|
||
|
||
- [x] 8.1 Переименование фото: `PATCH /photos/:id` (права автора/owner/admin, пустое имя 400) и инлайн-редактор в правой панели; событие `photo.updated`. Проверка: переименование сохраняется, пустое имя отклоняется, чужое фото — отказ, событие приходит.
|
||
- [x] 8.2 Удаление фото из просмотра с модальным подтверждением; переход к соседнему и обновление ленты. Проверка: после удаления лента не содержит удалённое фото.
|
||
- [x] 8.3 Полноэкранный просмотр (кнопка и `f`) с автоскрытием верхней/нижней панелей у кромок и стрелок (1/6 края, скрытие ~300 мс, в зуме скрыты); зум колесом к указателю без прокрутки (макс 200%), `Space` возвращает из любого зума во вписанный вид, в зуме подгружается оригинал. Проверка: поведение подтверждено в браузере (автоскрытие, зум к указателю, предел 200%, подмена превью→оригинал).
|
||
- [x] 8.4 Сворачиваемые разделы правой панели с сохранением состояния и раздел «Информация» (EXIF). Проверка: состояние сохраняется между сессиями, метаданные отображаются.
|
||
- [x] 8.5 Ресайз левой и правой панелей перетаскиванием с сохранением ширины. Проверка: ширина восстанавливается после перезагрузки.
|
||
- [x] 8.6 Лимит загрузки 100 МБ и понятные ошибки (клиентская проверка и `413 file_too_large`). Проверка: 60 МБ проходит разбор, превышение лимита даёт явную ошибку размера.
|
||
- [x] 8.7 Счётчики `photos_count`/`members_count` в ответе альбома. Проверка: `GET /api/albums` и `GET /api/albums/:id` содержат счётчики.
|
||
- [x] 8.8 Тема и хром: нейтральная тема — тёплый графит, порядок и значок переключателя, стилизация скроллбаров. Проверка: темы переключаются согласованно, скроллбары в тон темы.
|
||
- [x] 8.9 Извлечение EXIF: парсер заменён на `github.com/dsoprea/go-exif/v3` (все распознаваемые теги, нормализация чисел), хранение в `photos.exif` (миграция `000003_photo_exif`, jsonb `'{}'` по умолчанию), отдача в API фото; дата съёмки и GPS дублируются в `taken_at`/координаты. Проверка: реальное фото HUAWEI nova 15 Pro даёт 67 атрибутов; `go test ./...`.
|
||
- [x] 8.10 Бэкфилл метаданных существующих фото: команда `cmd/backfill-exif` + `storage.Get` + `photos.Store.BackfillEXIF` (тянет оригиналы из S3, заполняет `exif`/`taken_at`/координаты). Проверка: прогон обновил 25 фото, `taken_at` проставлен.
|
||
- [x] 8.11 Раздел «Информация» показывает все EXIF-атрибуты с русскими подписями, переводом enum и единицами (выдержка `1/N`, `f/N`, `N мм`, EV); служебные/дублирующие теги скрыты. Проверка: Playwright на реальном фото — 9/9.
|
||
|
||
## 9. Настройки информации пользователя
|
||
|
||
- [ ] 9.1 Миграция `000004_user_preferences` (up/down): `ADD COLUMN preferences jsonb NOT NULL DEFAULT '{}'`. Проверка: `make migrate` применяется, `\d users` показывает колонку, down её снимает.
|
||
- [ ] 9.2 `models.User` (+`Preferences`), `auth/store.go` (выборка `preferences` в `GetUserByID`, метод `UpdatePreferences`), `auth/handlers.go`/`service.go` (`Me` возвращает `preferences`; `PATCH /auth/me/preferences` с валидацией структуры, длины подписей/ключей и числа скрытых). Проверка: невалидный payload → 400, валидный сохраняется.
|
||
- [ ] 9.3 Маршрут `PATCH /auth/me/preferences` в `app.go` под middleware. Проверка: без токена 401, с токеном сохранение и чтение согласованы.
|
||
- [ ] 9.4 Frontend: `types.ts` (+`preferences`), auth-стор (+`preferences`, сохранение через API). Проверка: `cd frontend && npm run check`.
|
||
- [ ] 9.5 `PanelSection` — слот `actions` для шестерёнки; новый `PhotoInfoSettingsDialog` (список атрибутов, видимость, подписи, сброс к дефолту). Проверка: изменения применяются к разделу и сохраняются в профиле.
|
||
- [ ] 9.6 `AlbumPanel`: фильтрация атрибутов по оверрайдам, элемент «…» для показа скрытых (при отсутствии скрытых — не показывается). Проверка: скрытые сворачиваются, «…» раскрывает все, дефолт — все видимы.
|
||
- [ ] 9.7 Проверка: `cd frontend && npm run check`, `cd backend && go test ./...`, `make build`, `openspec validate "lightroom-workspace-ui"`.
|