docs: document T-Bank cache generation boundary

This commit is contained in:
2026-09-01 05:55:09 +03:00
parent adc369be79
commit 3d2bc1eac4
4 changed files with 28 additions and 6 deletions
+4 -3
View File
@@ -52,13 +52,14 @@ section; Portfolio и соседние sections остаются доступн
декоративны, не содержат пользовательских данных и не получают keyboard focus.
- Backend хранит успешный полный Snapshot для пары User и Brokerage Account свежим 30 секунд.
- После истечения fresh TTL последний Snapshot можно вернуть как fallback не более пяти минут только при `TBANK_UNAVAILABLE`; такой ответ имеет `isStale: true` и исходный `lastSuccessfulAt`.
- До cache hit backend разрешает текущее persisted T-Bank connection. Snapshot повторно используется только внутри непрозрачной generation этого connection; при cache miss доступ к Brokerage Account проверяется до финансовых T-Bank RPC.
- После истечения fresh TTL последний Snapshot той же generation можно вернуть как fallback не более пяти минут только при `TBANK_UNAVAILABLE`; такой ответ имеет `isStale: true` и исходный `lastSuccessfulAt`.
- `INVALID_TBANK_TOKEN` никогда не маскируется fallback-данными.
- Успешная замена или удаление T-Bank connection очищает Portfolio Snapshot User.
- Успешное создание, замена или удаление T-Bank connection меняет cache boundary. Сохранённый либо поздно завершившийся Snapshot предыдущей generation не выдаётся новому запросу; явная очистка при mutation остаётся best-effort освобождением памяти.
- Metadata по FIGI и подтверждённое отсутствие инструмента кэшируются в памяти 24 часа глобально; ошибки lookup не кэшируются.
- `409 TBANK_NOT_CONNECTED` и `400 INVALID_TBANK_TOKEN` ведут в настройки; `404` показывает возврат к списку счетов; `503 TBANK_UNAVAILABLE` и неизвестная ошибка позволяют повторить запрос, если нет fallback Snapshot.
Клиент использует успешный React Query-кэш 30 секунд, без polling, automatic retry и refetch при возврате фокуса. При фоновом обновлении уже полученный Portfolio остаётся видимым как **Client Cached Snapshot**; отдельные индикатор обновления и предупреждение с повторной попыткой показывают его состояние. Это не server-side fallback: `Portfolio.isStale` сообщает только о fallback Snapshot от backend и может отображаться одновременно с client refresh warning. Акции и облигации отображаются отдельными секциями с upstream-порядком внутри каждой; остальные активы остаются в отдельной секции. Карточки акций и облигаций выводятся одноколоночным grid при любой ширине экрана и показывают Instrument Logo слева от текстового блока (аватар 40px с фолбэком на первую букву отображаемого имени при отсутствии `logoName` или ошибке загрузки), идентификатор, количество, текущую стоимость и абсолютный доход со знаком `+`/`-` без текстовой подписи (ноль -- без знака) с цветовой индикацией знака (зелёный при положительном, красный при отрицательном). Карточка акции группирует логотип, имя, количество и ticker слева; при `ticker: null` вместо него показывает ISIN. Карточка облигации и плитка актива иного типа сохраняют ISIN. Стоимость с доходностью остаются справа, а средняя цена и доля показываются одной приглушённой строкой под разделителем. Карточки остальных активов остаются компактными плитками в адаптивном grid -- одна колонка на узком экране, 2-3 на широком, -- с текстовой подписью абсолютного дохода и без визуального логотипа даже при наличии `logoName`. Плитка показывает идентификатор, количество, текущую стоимость, абсолютный доход с цветовой индикацией знака (зелёный при положительном, красный при отрицательном), среднюю цену покупки, долю среди позиций той же валюты и ISIN. Доля -- display-only оценка, вычисляемая на клиенте как `currentValue` позиции к сумме `currentValue` позиций той же валюты, без Cash Balance; отсутствующие метрики скрываются, а не отображаются заглушкой. RUB форматируется как `₽`. Loaded view сохраняет адаптивное одноколоночное отображение на узком экране.
Клиент использует успешный React Query-кэш 30 секунд, без polling, automatic retry и refetch при возврате фокуса. Successful connection mutation сбрасывает Portfolio query вместе со всем brokerage subtree, поэтому предыдущий Snapshot не показывается во время загрузки новой generation. При обычном фоновом обновлении без смены connection уже полученный Portfolio остаётся видимым как **Client Cached Snapshot**; отдельные индикатор обновления и предупреждение с повторной попыткой показывают его состояние. Это не server-side fallback: `Portfolio.isStale` сообщает только о fallback Snapshot от backend и может отображаться одновременно с client refresh warning. Акции и облигации отображаются отдельными секциями с upstream-порядком внутри каждой; остальные активы остаются в отдельной секции. Карточки акций и облигаций выводятся одноколоночным grid при любой ширине экрана и показывают Instrument Logo слева от текстового блока (аватар 40px с фолбэком на первую букву отображаемого имени при отсутствии `logoName` или ошибке загрузки), идентификатор, количество, текущую стоимость и абсолютный доход со знаком `+`/`-` без текстовой подписи (ноль -- без знака) с цветовой индикацией знака (зелёный при положительном, красный при отрицательном). Карточка акции группирует логотип, имя, количество и ticker слева; при `ticker: null` вместо него показывает ISIN. Карточка облигации и плитка актива иного типа сохраняют ISIN. Стоимость с доходностью остаются справа, а средняя цена и доля показываются одной приглушённой строкой под разделителем. Карточки остальных активов остаются компактными плитками в адаптивном grid -- одна колонка на узком экране, 2-3 на широком, -- с текстовой подписью абсолютного дохода и без визуального логотипа даже при наличии `logoName`. Плитка показывает идентификатор, количество, текущую стоимость, абсолютный доход с цветовой индикацией знака (зелёный при положительном, красный при отрицательном), среднюю цену покупки, долю среди позиций той же валюты и ISIN. Доля -- display-only оценка, вычисляемая на клиенте как `currentValue` позиции к сумме `currentValue` позиций той же валюты, без Cash Balance; отсутствующие метрики скрываются, а не отображаются заглушкой. RUB форматируется как `₽`. Loaded view сохраняет адаптивное одноколоночное отображение на узком экране.
## Ограничения MVP