Таблицы тональных кривых в WASM-модуле лежали в статическом массиве, который
линковщик помещал в сегмент .data — такой сегмент в wasm доступен только для
чтения, поэтому запись в него не срабатывала, и модуль считал по мусорным
данным (отсюда «каша» вместо плавного градиента).
Теперь таблицы считает и передаёт JS: модуль не хранит состояния и только
применяет правки к пикселям. Дополнительно клиент сверяет модуль с эталонной
JS-реализацией на маленьком образце и, если они расходятся, доверяет JS.
Service worker больше не кэширует .wasm: старый модуль в кэше ломал
предпросмотр даже после обновления сборки.
Прокручиваемый контейнер просмотра не имел min-w-0, поэтому в зуме он
раздувался до ширины картинки вместо того, чтобы прокручиваться: фото
нельзя было двигать влево-вправо. Та же правка применена к обёрткам просмотра.
Пробел теперь зумит к центру фото: 100% от оригинала с центрированием
изображения в просмотре.
Механика зума переписана:
- Пробел зумит фото до 100% от оригинала (и обратно к вписанному виду).
- Колесо мыши зумит к курсору: точка под указателем остаётся на месте при
любом изменении масштаба.
- Стрелки влево/вправо переключают фото и в зуме — панорамирование осталось
за мышью.
- Масштаб и позиция считаются от размеров оригинала, а не от текущей рамки,
поэтому зум не «уезжает» и картинка корректно центрируется.
Клиентский предпросмотр был включён только в зуме, поэтому в обычном виде
просмотра фото оставалось без правок. Теперь холст рисует ту часть картинки,
что попала во вьюпорт, в любом режиме, а под ним лежит неотредактированная
версия фото — так правки не применяются дважды.
Новый алгоритм редактирования: обработка идёт только на клиенте.
- Фильтры «Свет» считает WASM-модуль на Rust (frontend/wasm/light, 1.5 КБ):
он строит тональные кривые и обрабатывает пиксели. При недоступности модуля
используется JS-реализация с тем же результатом — расхождение проверяется
тестом.
- Просмотр рисует видимую область фото с правками на canvas поверх
исходной версии, поэтому изменения видны сразу, без ожидания рендера.
- Правки уходят на сервер не сразу: через десять секунд без изменений или при
переключении фото. Сервер сохраняет параметры и готовит варианты сетки и
полноразмерную версию в фоне, о готовности сообщает через SSE — клиент
подтягивает готовую версию.
Вместо полноразмерной отредактированной версии просмотр запрашивает у сервера
только видимую область кадра: координаты относительные, поэтому запрос не
зависит от размера оригинала. Рендер куска занимает около 0.15 с против 0.8 с
для всей картинки, а полноразмерная версия больше не готовится вовсе.
Зум больше не сбрасывается, когда приходит отрендеренная версия правок:
состояние просмотра сбрасывается только при смене самого фото.
В правой панели фото появилась вкладка «Редактирование» с инструментом «Свет»:
ползунки экспозиции, контраста, светов, теней, белых и чёрных и точечная кривая
с выбором канала. Правки сохраняются автоматически, переключатель «Сброс»
удаляет параметры правок и отредактированную версию фото.
Редактирование неразрушающее: параметры хранятся в метаданных фото, а
отредактированная версия рендерится в фоне отдельным объектом (edit, edit_thumb,
edit_medium, edit_preview) рядом с исходными вариантами. Оригинал не меняется,
скачивание отдаёт исходный файл. Сетка, просмотр и обложки альбомов показывают
отредактированную версию, когда она отрендерена.
Кнопке «На весь экран» в верхней панели добавлен хоткей Ctrl+F. Хоткей `f`
полноэкранного просмотра больше не срабатывает на Ctrl+F: модификаторы
проверяются явно, поэтому сочетание свободно для кнопки каркаса.
При переходе стрелками влево/вправо активная миниатюра нижней ленты уходила
за край и оставалась невидимой. Теперь лента прокручивается к текущему фото,
удерживая его плитку в поле зрения.
В режиме просмотра фотографии (обычный и полноэкранный) клавиши + и - меняют
масштаб изображения на шаг, сохраняя точку в центре просмотра. Пока открыт
просмотр, эти клавиши больше не меняют размер миниатюр в сетке.
Add video as a media kind in photos: streaming upload with per-kind
limits, ffprobe metadata, poster frame reusing the image variant
pipeline, and a playback object (remux when web-compatible, else
h264/yuv420p/aac transcode). Video jobs run on a separate pool with a
long lease. Frontend gains a video viewer, grid badge/duration and
video upload.
- Detect 'pano360' tag and open the photo in a spherical viewer
- Mini-WebGL equirect viewer (no deps): drag to look, wheel FOV, Space reset
- Load original as origin-clean bitmap; fall back to flat image without WebGL
- 360 indicator in viewer and badge on grid tiles
- 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)
- remove all hide timers in Viewer; top/bottom bars and arrows now show/hide immediately
- arrows reveal only within the viewer bounds (no longer stay visible when the pointer is over the left/right panels)
- f (and the viewer button) toggle the viewer's own fullscreen via nested fullscreen, so f works inside app-level fullscreen without turning it off
- 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
- in zoom, preload the full-resolution original and swap it in once loaded
(preview is shown until then); revert to preview on zoom exit
- Space returns to the fitted view from any zoom level; from fit it goes to 100%
- derive zoom scale from the photo's true dimensions so 100% is accurate
- fullscreen: auto-hide top toolbar and filmstrip, revealed at top/bottom
screen edge and hidden 1s after the cursor leaves
- arrows < >: edge-reveal in both normal and fullscreen; hide 300ms after
leaving; hidden while zoomed
- zoom: wheel zooms only (no scroll, overscroll contained), anchored to the
pointer, capped at 200%; Space toggles 100%/fit; pan via drag/scroll
- fullscreen toggle button + "f" hotkey (both layouts)
- wheel zoom anchored to cursor; Space toggles 100% (1:1)
- pan when zoomed via drag/scrollbars; reset zoom on photo change
- hotkeys ignored while typing in inputs
Backend:
- add PATCH /api/photos/:id to rename original_filename (uploader/owner/admin)
- emit photo.updated SSE event
Frontend:
- inline filename editor in the right panel
- viewer: back button (arrow_back) on the left, delete on the right
- delete confirmed via a modal dialog; filmstrip updates after deletion
Backend:
- expose cover thumbnail URL on album responses (LEFT JOIN + presign)
- auto-assign cover only when empty; FK cover_photo_id -> photos ON DELETE SET NULL
- add photos_count / members_count to album responses; cover repo test
Frontend:
- split layouts into (app) shell (resizable rail + contextual panel) and (public)
- album rail + create-album dialog; album panel with cover picker / make-cover
- photo viewer route with filmstrip, replacing lightbox; share reuses it
- grid density slider, toolbar + drag-and-drop upload, dark graphite default
OpenSpec: add lightroom-workspace-ui change (proposal, design, specs, tasks)