chore(openspec): archive fix-trash-counter-sync and sync main specs
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# 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 и данных.
|
||||
Reference in New Issue
Block a user