- backend: soft delete + trash (photos.deleted_at, migration 000005), GET /trash, restore, permanent delete with storage-key refcount (S3 objects removed only when no other album references them); batch endpoints photo-batch/{delete,move,copy} - metadata only, copies clone rows sharing storage keys
- frontend: Ctrl/Shift multiselect; right-panel 'Действия' as icon text rows (Открыть (просмотр), Переместить, Копировать, Удалить); drag selected photos onto rail album -> TransferDialog; /trash route; trash entry as last item in the left rail; empty right panel hidden on overview
- frontend: compact top bars (app header and viewer), compact theme switcher
- openspec: sync lightroom-workspace-ui artifacts (photos/ui-workspace specs, design #20-24, tasks 9.11-9.17)
17 KiB
17 KiB
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(jsoncover_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.