Files
ds-galo/openspec/changes/lightroom-workspace-ui/tasks.md
T
iu 61b45b2742 feat: context menus, unified photo panel, tags, album trash and counters
- backend: deleting an album moves its photos to the trash without losing files (photos.album_id nullable, FK ON DELETE SET NULL, migration 000006; dropped S3 prefix delete); restore accepts an album target (album_required when the album is gone); user-scoped tags (000007) with GET /tags, POST/DELETE /photos/:id/tags, tags included in photo responses; GET /trash/count
- frontend: right-click context menus for photos (grid + viewer) and albums (rail + overview); single shared PhotoPanel reused in viewer and gallery selection (not a copy); viewer download/delete moved to the panel 'Действия'; transparent compact viewer top bar; trash entry as last rail item with per-album photo counts and trash count; album rename/delete dialogs
- openspec: sync lightroom-workspace-ui artifacts (specs photos/ui-workspace, design #24-30, tasks 9.17-9.24)
2026-10-10 22:45:41 +03:00

20 KiB
Raw Blame History

Tasks

1. Backend: обложка и целостность

  • 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-миграция снимает его.
  • 1.2 Добавить CoverThumbURL в models.Album (json cover_thumb_url,omitempty). Проверка: cd backend && go build ./... проходит.
  • 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 без неё.
  • 1.4 В albums/service.go пресигнить cover_thumb_key в CoverThumbURL в List и Get. Проверка: GET /api/albums в ответе содержит cover_thumb_url у альбома с обложкой.
  • 1.5 В photos/service.go вызывать SetCover только при cover_photo_id IS NULL. Проверка: загрузка двух фото в новый альбом оставляет обложкой первое; после удаления фото-обложки cover становится NULL, следующая загрузка ставит новый.
  • 1.6 Покрыть тестами store-join обложки и автообложку; проверить cd backend && go test ./....

2. Frontend: темы и каркас

  • 2.1 Добавить токены --chrome/--canvas, перенастроить dark в графит и сделать его дефолтом (при отсутствии сохранённого выбора). Проверка: cd frontend && npm run check проходит, все три темы визуально согласованы.
  • 2.2 Добавить cover_thumb_url в lib/types.ts и недостающие иконки (обложка, плотность/меню) в lib/icons/registry.ts. Проверка: npm run check без ошибок типов.
  • 2.3 Разбить +layout.svelte на группы (app)/(public) и ввести AppShell (топбар, слот рейла, слот панели, центр = маршрут). Проверка: навигация между альбомами не перезагружает рейл; /login, /invites/[token], /share/[token] рендерятся без каркаса.
  • 2.4 Реализовать адаптивный каркас: рейл → drawer, панель → сворачиваемая секция, лента → горизонтальный скролл на узкой ширине. Проверка: при ширине меньше порога каркас управляем без горизонтального overflow.

3. Рейл и создание альбома

  • 3.1 Компонент AlbumRail: список альбомов с обложками, плейсхолдер, подсветка активного. Проверка: активный альбом выделен, остальные показывают обложку/плейсхолдер.
  • 3.2 Кнопка создания + с хинтом и модалка ввода названия (замена инлайн-формы). Проверка: создание альбома через модалку, новый альбом становится активным; пустое название не создаёт альбом.

4. Контекстная панель и обложки

  • 4.1 Компонент AlbumPanel: превью обложки, название, счётчики, переходы к участникам/настройкам/ссылкам; управление обложкой только для owner/admin. Проверка: viewer не видит управление обложкой.
  • 4.2 Модальный CoverPicker (сетка миниатюр) + PATCH /albums/:id с cover_photo_id/clear_cover. Проверка: назначение и сброс обложки отражаются в рейле, карточке и панели.
  • 4.3 Выбор обложки выполняется только из правой панели (модальный пикер); действие «Сделать обложкой» в тулбаре просмотра не используется. Проверка: обложка задаётся и сбрасывается из панели, в тулбаре просмотра кнопки нет.

5. Центр: сетка, плотность, загрузка

  • 5.1 Сетка альбомов edge-to-edge с реальными обложками вместо карточек-заглушек. Проверка: карточки показывают cover_thumb_url, при отсутствии — плейсхолдер.
  • 5.2 Регулятор плотности миниатюр в тулбаре сетки с сохранением в localStorage. Проверка: размер меняется и восстанавливается после перезагрузки.
  • 5.3 Загрузка: иконочная кнопка в тулбаре, полноэкранный drag-overlay, зона-подсказка в пустом альбоме; прогресс компактной полосой. Проверка: перетаскивание и выбор файлов загружают фото, пустой альбом показывает подсказку.

6. Просмотр как маршрут

  • 6.1 Маршрут albums/[id]/photos/[photoId] и компонент Viewer (крупное фото, filmstrip, панель метаданных/комментариев, тулбар, клавиши ←/→/Esc). Проверка: переход из сетки открывает маршрут, адрес отражает фото, навигация синхронна с filmstrip.
  • 6.2 Переиспользовать Viewer на share/[token] вместо инлайн-лайтбокса. Проверка: публичный просмотр использует тот же компонент без рейла/панели.

7. Интеграция

  • 7.1 Пройти сценарий: создать альбом → загрузить фото → задать обложку → открыть просмотр → открыть share-ссылку; проверить поведение на десктопе и узком экране.
  • 7.2 Проверить make build (сборка фронтенда в backend/static, статический Go-бинарник) и openspec validate "lightroom-workspace-ui".

8. Дополнительно реализовано (после планирования)

  • 8.1 Переименование фото: PATCH /photos/:id (права автора/owner/admin, пустое имя 400) и инлайн-редактор в правой панели; событие photo.updated. Проверка: переименование сохраняется, пустое имя отклоняется, чужое фото — отказ, событие приходит.
  • 8.2 Удаление фото из просмотра с модальным подтверждением; переход к соседнему и обновление ленты. Проверка: после удаления лента не содержит удалённое фото.
  • 8.3 Полноэкранный просмотр (кнопка и f) с автоскрытием верхней/нижней панелей у кромок и стрелок (1/6 края, в зуме скрыты); поведение без таймеров — панели и стрелки показываются/скрываются сразу, стрелки только в границах вьюера; зум колесом к указателю без прокрутки (макс 200%), Space возвращает из любого зума во вписанный вид, в зуме подгружается оригинал. Проверка: поведение подтверждено в браузере (автоскрытие, зум к указателю, предел 200%, подмена превью→оригинал).
  • 8.4 Сворачиваемые разделы правой панели с сохранением состояния и раздел «Информация» (EXIF). Проверка: состояние сохраняется между сессиями, метаданные отображаются.
  • 8.5 Ресайз левой и правой панелей перетаскиванием с сохранением ширины. Проверка: ширина восстанавливается после перезагрузки.
  • 8.6 Лимит загрузки 100 МБ и понятные ошибки (клиентская проверка и 413 file_too_large). Проверка: 60 МБ проходит разбор, превышение лимита даёт явную ошибку размера.
  • 8.7 Счётчики photos_count/members_count в ответе альбома. Проверка: GET /api/albums и GET /api/albums/:id содержат счётчики.
  • 8.8 Тема и хром: нейтральная тема — тёплый графит, порядок и значок переключателя, стилизация скроллбаров. Проверка: темы переключаются согласованно, скроллбары в тон темы.
  • 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 ./....
  • 8.10 Бэкфилл метаданных существующих фото: команда cmd/backfill-exif + storage.Get + photos.Store.BackfillEXIF (тянет оригиналы из S3, заполняет exif/taken_at/координаты). Проверка: прогон обновил 25 фото, taken_at проставлен.
  • 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".
  • 9.8 Базовый набор атрибутов по умолчанию при пустых настройках (модель visible вместо hidden); в диалоге — иконки «отметить все»/«снять все» вместо «Показать всё»; «Сбросить» удаляет настройки из профиля (PATCH {}). Проверка: Playwright — дефолт даёт базовый набор и «…», снятие/отметка всех, сброс очищает профиль (12/12).
  • 9.9 Выбор фото в сетке: одиночный клик выделяет фото рамкой и показывает его сведения в правой панели, двойной — открывает просмотр; стор selection, сброс при смене альбома. Проверка: Playwright — выделение без навигации, панель показывает инфо, двойной клик открывает просмотр (5/5).
  • 9.10 Вьюер и модалки: убраны таймеры автоскрытия (панели/стрелки мгновенно), стрелки появляются только в границах вьюера, f/кнопка переключают вложенный fullscreen вьюера (не выключают режим приложения); модалки рендерятся порталом в document.body (центрирование во вьюпорте). Проверка: Playwright — стрелки 4/4, fullscreen+f 6/6, центрирование 2/2.
  • 9.11 Корзина: миграция 000005_photo_trash (deleted_at), мягкое удаление (DELETE /photos/:id), GET /trash, POST /photos/:id/restore, DELETE /photos/:id/permanent с удалением объектов S3 только при отсутствии ссылок на storage_key. Проверка: удаление → в корзине, восстановление возвращает, копия сохраняет объект.
  • 9.12 Перенос/копирование: POST /photo-batch/{delete,move,copy}, клон записи с общими объектами S3 (метаданные), события SSE для источника и цели. Проверка: копия даёт N фото в цели, перенос убирает из источника.
  • 9.13 Фронтенд: стор selection (Ctrl/Shift-мультивыбор), действия в панели «Удалить/Переместить/Копировать», drag выделенных на альбом рейла, глобальный TransferDialog. Проверка: Playwright — мультивыбор и copy 9/9, drag+move 4/4.
  • 9.14 Рейл: вход «Корзина» последним элементом списка с иконкой, скрытие пустой правой панели (panelVisible), маршрут /trash с восстановлением и окончательным удалением. Проверка: в рейле есть вход в корзину, на обзоре нет правой панели.
  • 9.15 Проверка: cd frontend && npm run check, cd backend && go test ./..., make build, openspec validate "lightroom-workspace-ui".
  • 9.16 Раздел «Действия» в панели — пункты-надписи с иконками (не кнопки): «Открыть (просмотр)», «Переместить», «Копировать», «Удалить»; надпись «Выбрано:» убрана. Проверка: Playwright — «Открыть (просмотр)» открывает просмотр, раздел присутствует (4/4).
  • 9.17 Компактный верхний хром: верхняя панель приложения и верхняя панель режима просмотра сведены к минимуму (узкие отступы, иконочные кнопки size-4, компактный переключатель темы). Проверка: высота <header> ~31px, верхней панели просмотра ~29px; npm run check.
  • 9.18 Действия просмотра перенесены в правую панель: в разделе «Действия» — «Скачать», «Переместить», «Копировать», «Удалить»; из верхней панели просмотра скачивание и удаление убраны; удаление через стор viewerActions и страницу просмотра. Проверка: Playwright — верх без скачать/удалить, панель с действиями, удаление из панели переходит к соседнему фото (9/9).
  • 9.19 Единая панель фото: содержимое для одного фото вынесено в общий компонент PhotoPanel, переиспользуемый в просмотре и при одиночном выборе в сетке (не копия); мультивыбор — только «Действия». Проверка: Playwright — просмотр 9/9, выбор 5/5, мультивыбор 9/9; npm run check.
  • 9.20 Контекстное меню фото (правая кнопка) в сетке и просмотре с пунктами из «Действий»; общий стор contextMenu и глобальный ContextMenu. Проверка: Playwright — меню в сетке (5 пунктов), меню в просмотре (4 пункта), «Открыть» из меню и «Переместить» → диалог (12/12).
  • 9.21 Контекстное меню альбома («Переименовать»/«Удалить») в рейле и на обзоре; диалоги в AlbumActionsDialogs. Проверка: Playwright — меню, переименование, удаление (4/4).
  • 9.22 Удаление альбома помещает фото в корзину без потери файлов (FK photos.album_id → ON DELETE SET NULL, миграция 000006); восстановление без альбома предлагает выбрать/создать альбом (album_required + POST /photos/:id/restore {album_id}). Проверка: Playwright — удалить альбом → фото в корзине, диалог восстановления, фото в новом альбоме (7/7).
  • 9.23 Счётчики в рейле: число фото у альбома и число элементов корзины (GET /trash/count, стор trash); обновление счётчика альбома после загрузки/удаления. Проверка: Playwright — альбом 2, корзина 0→1 (3/3).
  • 9.24 Метки фото (область — пользователь): таблицы tags/photo_tags (миграция 000007), GET /tags?q=, POST /photos/:id/tags, DELETE /photos/:id/tags/:tag_id, метки в ответе фото; раздел «Метки» в панели с чипами, поиском и созданием. Проверка: API — создание/поиск/снятие; Playwright — создание, снятие, выбор из поиска (4/4).