# 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 и данных.