Files
ds-galo/openspec/changes/fix-trash-counter-sync/design.md
T
iu 713c1ca6d3 fix(ui): keep trash counter in sync with trash changes
Reload the rail trash count after every operation that changes the
trash contents, not just navigation and empty-trash: moving photos to
trash (grid, panel, viewer), restoring, permanent delete and deleting
an album. Previously the counter stayed stale until the next route
change.
2026-10-12 12:58:18 +03:00

62 lines
4.7 KiB
Markdown

# Design
## Context
См. `proposal.md` — Why. Существенное:
- `TrashStore` (`frontend/src/lib/stores/trash.svelte.ts`) держит `count` и умеет `load()`
(запрос `GET /api/trash/count`) и `reset()`.
- `AlbumRail.svelte` показывает `trash.count` и вызывает `trash.load()` только в `$effect`
при смене `$page.url.pathname` и при аутентификации.
- `trash/+page.svelte` вызывает `trash.load()` только в `emptyAll`; `restore`,
`confirmRestore`, `remove` (окончательное удаление) обновляют лишь свой список.
- Удаление фото разбросано: `deleteBatch` в сетке и панели, прямой `api(...DELETE)` в
просмотре; удаление альбома — в `AlbumActionsDialogs` и `settings/+page.svelte`.
- Серверный `TrashCount` считает мягко удалённые фото, доступные пользователю, и корректен.
## Goals / Non-Goals
**Goals:**
- Счётчик корзины в рейле отражает актуальное состояние после любой операции, меняющей
содержимое корзины, без перезагрузки.
- Единообразный, предсказуемый способ обновления во всех точках.
**Non-Goals:**
- Синхронизация счётчика между сессиями/устройствами (SSE-событий корзины нет).
- Изменения backend и Android.
- Оптимистичные локальные инкременты без сверки с сервером.
## Decisions
### 1. Обновлять счётчик из сервера (`trash.load()`), а не инкрементировать локально
После каждой успешной мутации вызываем `trash.load()`. Это даёт точное значение для всех
случаев, включая пакетное удаление и удаление альбома (где число фото заранее неизвестно
точно), и повторяет уже принятый в `emptyAll` паттерн. Альтернатива с оптимистичным
`+n`/`-1` требует знать точное число и расходится при частичных ошибках; альтернатива с
общим сигналом (как `photo-updates`) добавляет лишний слой ради одного значения.
### 2. Вызывать обновление в каждой точке мутации, после успешного await
Обновление идёт после успешного ответа (в `try`, не в `finally`), чтобы при ошибке
счётчик не менялся. Точки: пакетное удаление (сетка), удаление из панели/просмотра,
удаление в просмотре, восстановление и окончательное удаление в корзине, удаление
альбома (диалог и настройки). Это устраняет исходный дефект и родственные рассинхроны
той же природы.
### 3. Оставить асинхронность неблокирующей
`trash.load()` уже глотает ошибки и не мешает основному действию; вызов не await-им
блокирующе там, где это меняет UX (в остальных местах допустимо `await` для
детерминизма тестов). Достаточно, чтобы счётчик обновился к моменту отрисовки.
## Risks / Trade-offs
- [Лишний `GET /api/trash/count` на каждую мутацию] → незначительная нагрузка, сопоставимо
с текущим поведением при навигации и очистке корзины.
- [Гонка при параллельных мутациях] → последний ответ сервера побеждает; значение
остаётся корректным, т.к. берётся из источника истины.
- [Разброс точек вызова — риск пропустить новую в будущем] → спека фиксирует инвариант;
при добавлении новых операций с корзиной требование распространяется на них.
## Migration Plan
Не требуется: изменения только во фронтенде, без изменения API и данных.