chore(openspec): archive thumbnail-variants and sync main specs
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
См. `proposal.md` для мотивации. Технические факты, формирующие подход:
|
||||
|
||||
- Генерация вариантов — `photos/pipeline.go:buildVariants`: `thumb = Fit(200,200)`,
|
||||
`preview = Fit(1200,1200)`, WebP q82. Оба кропа вписывают по длинной стороне.
|
||||
- Метаданные имеют две реализации (Postgres и SQLite) с общей контрактной проверкой
|
||||
`internal/repo/contract`; SQL дублируется (`photos/store.go`, `photos/sqlite_store.go`).
|
||||
- Роли заданы жёстко: `photo_objects.role CHECK (role IN ('original','thumb','preview'))`
|
||||
(`migrations/000008`, `migrations/sqlite/000001`).
|
||||
- Варианты генерирует фоновый воркер из очереди `variant_jobs`; лишние объекты удаляются
|
||||
сборщиком мусора по отсутствию ссылок (`photos/worker.go`).
|
||||
- Фронтенд-сетка берёт только `thumb_url` (`PhotoGrid.svelte:78`); обложки — только
|
||||
`cover_thumb_url` (`albums/store.go` джойнит `role='thumb'`).
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Сетка фото и обзор альбомов показывают изображение без апскейла при любом выбранном размере.
|
||||
- Единое поведение на Postgres и SQLite; контрактные тесты покрывают новую роль.
|
||||
- Бэкфилл существующих фото без ручных операций.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Динамоческое ресайзирование на лету / image-proxy.
|
||||
- Иные форматы (AVIF) или согласование форматов.
|
||||
- Изменение `preview` (просмотрщик) и оригинала.
|
||||
- Замер точной плотности DPR-пикселей и «идеальный» выбор варианта — допускается запас.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Набор вариантов.** `thumb = Fill(200,200)` (квадрат), `medium = Fill(800,800)` (квадрат),
|
||||
`preview = Fit(1200,1200)`. Причина: плитка сетки квадратная (`aspect-square object-cover`),
|
||||
квадратный кроп исключает лишний апскейл короткой стороны; просмотрщик не квадратный, поэтому
|
||||
`preview` остаётся вписанным. Альтернатива — оставить `thumb` вписанным: отклонена, короткая
|
||||
сторона выходит < 200 и мылит уже мелкие плитки.
|
||||
- **Роль `medium` в `photo_objects`.** Роли маппятся 1:1 на поля API и на привязки; расширяем
|
||||
ограничение значением `medium`. Альтернатива — отдельная таблица вариантов: избыточна для
|
||||
одного размера.
|
||||
- **`srcset` + `sizes`.** Ширины `200w/800w/1200w`; `sizes` берётся из фактической ширины сетки
|
||||
(`bind:clientWidth` на `<ul>`, делённой на число колонок). Альтернатива — оценка по `100vw`:
|
||||
неточна из-за рейла и правой панели, ведёт к перекачке 1200.
|
||||
- **Бэкфилл через очередь.** Миграция делает `INSERT ... SELECT id FROM photos` в
|
||||
`variant_jobs`; воркер пересоздаёт роли и перепривязывает, старые объекты уходят в GC по
|
||||
refcount. Альтернатива — воркер, пропускающий уже готовые роли: требует версионирования
|
||||
вариантов; альтернатива — отдельный `cmd/backfill`: лишний код. Выбран самый простой путь.
|
||||
- **Обложка.** Джойн medium-варианта обложки с откатом на `thumb`, если medium ещё не готов;
|
||||
отдельного поля «preview обложки» не вводим.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Перегенерация `preview` впустую при бэкфилле] → воркер работает батчами и параллельно
|
||||
(настраивается); лишние объекты удаляются сборщиком; разовая стоимость.
|
||||
- [Рост хранилища на один объект 800px на фото] → WebP q82 скромного размера; приемлемо.
|
||||
- [Неточный `sizes`] → запас в пользу резкости; при отсутствии `medium_url` вариант просто не
|
||||
попадает в `srcset`, сетка падает на доступные (thumb/preview).
|
||||
- [SQLite не умеет `ALTER CHECK`] → миграция пересоздаёт `photo_objects`; на больших таблицах
|
||||
копирование строк. Приемлемо для текущего масштаба, выполняется в окне обслуживания.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Применить миграцию: расширить `CHECK` роли, налить `variant_jobs` для существующих фото.
|
||||
2. Выкатить код: воркер генерирует `medium` и квадратный `thumb`; API отдаёт новые поля.
|
||||
3. Воркер разбирает очередь; сетка показывает `srcset` с откатом на доступные варианты.
|
||||
4. Откат: сначала код (API перестаёт отдавать medium, воркер не генерирует); затем down-миграция
|
||||
восстанавливает `CHECK`, предварительно удалив привязки `medium`.
|
||||
|
||||
## Open Questions
|
||||
|
||||
Нет.
|
||||
Reference in New Issue
Block a user