docs: describe quiet reading and visible source state (#51)
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 13s

ADR 021 фиксирует решение о тихом чтении и видимом состоянии источника; ui-resilience и затронутые domains приведены к AS-IS.
This commit was merged in pull request #52.
This commit is contained in:
2026-10-17 22:11:49 +03:00
parent 11f819ed31
commit e8e3ef6285
9 changed files with 120 additions and 30 deletions
@@ -0,0 +1,66 @@
# 021. Тихое чтение и редкие подтверждения
**Status:** Accepted. Заменяет manual-refresh часть
[ADR 019](019-hourly-reads-and-online-instrument-reference.md) на стороне
интерфейса и уточняет клиентскую часть
[ADR 007](007-react-query-fetch-policy.md). Не меняет владельцев обновления
и границы чтения из ADR 019.
## Context
Экраны просмотра перекладывали на пользователя контроль над получением данных:
кнопки «Обновить» и «Повторить», индикатор фонового обновления и сообщение
о неудачной синхронизации повторялись в разных разделах. Пользователь приходит
смотреть финансы, а не обслуживать загрузку; восстановление после временного
сбоя не должно зависеть от нажатия кнопки.
При этом состояние самих данных остаётся значимым: время последнего успешного
результата источника, устаревание и исход часовой попытки влияют на доверие
к показанным суммам и не могут быть просто скрыты.
## Decision
- Автоматическое чтение клиента не сообщает о себе. Экраны просмотра не
показывают кнопок ручного обновления и повтора, индикаторов фонового
перечитывания и технических предупреждений о собственном запросе.
- Временные сбои GET восстанавливаются сами: ограниченный срок ожидания запроса
и не более двух повторов с возрастающими паузами, затем возврат к штатной
работе при появлении результата. Пользователь не инициирует восстановление.
- Состояние источника из `meta` остаётся видимым. Время последнего успешного
результата, признак устаревания, исход последней часовой попытки и неполнота
показываются текстом либо доступно именованным элементом, а не только цветом.
Это свойство данных, а не отчёт о техническом процессе, и оно не превращается
в действие пользователя.
- Доступный результат сохраняется при обновлении и временной ошибке. Неполнота
и подтверждённая пустота остаются различимыми: неизвестная сумма не становится
нулём, незавершённый расчёт не выдаётся за окончательный, незагруженный список
не выдаётся за отсутствие записей.
- Первичное ожидание ограничено по времени и показывает структуру будущего
содержимого. По его истечении экран переходит в спокойную локальную
недоступность вместо бесконечной загрузки и сам заменяет её содержимым после
восстановления. Сбой одного блока не скрывает остальные доступные блоки.
- Ошибки явных действий пользователя сохраняются и остаются видимыми. Решение
касается только чтения; отправка формы, изменение данных и сброс фильтра
по-прежнему сообщают результат, в том числе неуспешный.
## Consequences
Пользователь не может ускорить обновление вручную: нормальным владельцем
обновления остаётся часовой обход из ADR 019, и отсутствие кнопки не меняет его
срок. Если источник недоступен долго, экран показывает прежние данные с
обозначением устаревания, а не приглашение повторить.
Автоматический повтор ограничен: он покрывает короткие сетевые сбои, но не
длительную недоступность. Ошибки доступа, домена и отмены не повторяются,
mutations не повторяются вовсе.
Состояние источника и состояние клиентского чтения описывают разные вещи и
не подменяют друг друга. `Portfolio.isStale` сохраняет смысл возраста снимка;
ошибка клиентского перечитывания его не меняет.
Детали поведения — в
[ожидании и восстановлении интерфейса](../architecture/cross-cutting/ui-resilience.md)
и [свежести данных](../architecture/cross-cutting/data-freshness.md).
Согласованные требования принадлежат workspace-спецификациям
`frontend-user-feedback`, `frontend-data-resilience` и
`frontend-loading-presentation`.
+1
View File
@@ -25,3 +25,4 @@
- [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md) - [018 — Минимальная runtime-координация](018-minimal-runtime-coordination.md)
- [019 — Часовой обход и онлайн-справочник инструментов](019-hourly-reads-and-online-instrument-reference.md) - [019 — Часовой обход и онлайн-справочник инструментов](019-hourly-reads-and-online-instrument-reference.md)
- [020 — Evidence for hourly sync read boundaries](020-hourly-sync-verification-evidence.md) - [020 — Evidence for hourly sync read boundaries](020-hourly-sync-verification-evidence.md)
- [021 — Тихое чтение и редкие подтверждения](021-quiet-reading-and-rare-confirmations.md)
+26 -10
View File
@@ -16,11 +16,17 @@
пока неизвестно, доступен public или protected маршрут. При загрузке пока неизвестно, доступен public или protected маршрут. При загрузке
отложенного маршрута уже доступный каркас остаётся видимым. отложенного маршрута уже доступный каркас остаётся видимым.
Фоновое обновление и действие пользователя сохраняют видимые данные или Фоновое чтение не сообщает о себе: оно сохраняет видимые данные без индикации
элемент управления с компактной индикацией процесса. В частности, процесса и без технического предупреждения. В частности,
[клиентский снимок](data-freshness.md#клиентский-и-серверный-снимки) счетов или [клиентский снимок](data-freshness.md#клиентский-и-серверный-снимки) счетов или
портфеля остаётся доступным после ошибки refetch; сообщение и повтор портфеля остаётся доступным после ошибки refetch, и эта ошибка не означает
относятся к обновлению, а не к отсутствию инвестиций. отсутствие инвестиций. Действие пользователя сохраняет видимые данные или
элемент управления и сообщает свой результат, включая неуспешный.
Первичное ожидание ограничено по времени. По его истечении область переходит
в спокойную локальную недоступность вместо бесконечной загрузки и сама
заменяет её содержимым после восстановления. Решение принято в
[ADR 021](../../adr/021-quiet-reading-and-rare-confirmations.md).
Серверная полнота не выводится из query lifecycle. Missing показывает Серверная полнота не выводится из query lifecycle. Missing показывает
skeleton только отсутствующих секций, partial — подтверждённые строки и skeleton только отсутствующих секций, partial — подтверждённые строки и
@@ -28,20 +34,30 @@ skeleton только отсутствующих секций, partial — по
готовятся независимо. Только complete empty означает отсутствие операций. готовятся независимо. Только complete empty означает отсутствие операций.
Время источника не заменяется временем получения HTTP-ответа. Время источника не заменяется временем получения HTTP-ответа.
Ручное обновление сохраняет показанный результат и applied filters; кнопка Экраны просмотра не предлагают ручное обновление или повтор чтения: временные
недоступна во время уже запрошенной работы. Ошибка transport отличается от сбои GET восстанавливаются сами ограниченным числом повторов, сохраняя
показанный результат и applied filters. Ошибка transport отличается от
ошибки source work. Status observation прекращается после terminal outcome ошибки source work. Status observation прекращается после terminal outcome
или ухода; само истечение TTL не запускает импорт. Известный access loss или ухода; само истечение TTL не запускает импорт. Известный access loss
очищает personal view, включая защиту от позднего ответа. очищает personal view, включая защиту от позднего ответа.
Состояние источника при этом видимо: время последнего успешного результата,
устаревание, исход последней часовой попытки и неполнота показаны текстом либо
доступно именованным элементом рядом с данными, к которым относятся. Это
свойство данных, а не отчёт о техническом процессе, и оно не требует действия.
## Известная ошибка сценария ## Известная ошибка сценария
Ошибки счетов, портфеля и подключения T-Bank остаются локальными: рядом Ошибки счетов, портфеля и подключения T-Bank остаются локальными: затронутый
с затронутыми данными доступны предусмотренные сценарием retry и navigation блок показывает собственное состояние и не скрывает успешно отображённые
actions. Устаревший серверный Portfolio Snapshot показывает сохранённый соседние блоки. Устаревший серверный Portfolio Snapshot показывает сохранённый
портфель с предупреждением. Он не превращается в глобальный fallback. портфель с обозначением устаревания. Он не превращается в глобальный fallback.
Ошибка источника не означает нулевой баланс или отсутствие операций. Ошибка источника не означает нулевой баланс или отсутствие операций.
Предусмотренные сценарием navigation actions сохраняются: подтверждённая потеря
доступа предлагает восстановить подключение, а `404` — вернуться к списку
счетов. Это навигация и явные действия пользователя, а не повтор чтения.
## Непойманная ошибка маршрута ## Непойманная ошибка маршрута
Непойманная runtime-ошибка при отображении public или protected route Непойманная runtime-ошибка при отображении public или protected route
+2 -2
View File
@@ -62,8 +62,8 @@
- `409 TBANK_NOT_CONNECTED`: список не запрашивается у T-Bank, экран ведёт в настройки. - `409 TBANK_NOT_CONNECTED`: список не запрашивается у T-Bank, экран ведёт в настройки.
- `400 INVALID_TBANK_TOKEN`: сохранённый токен не удаляется, экран ведёт в настройки для замены или удаления. - `400 INVALID_TBANK_TOKEN`: сохранённый токен не удаляется, экран ведёт в настройки для замены или удаления.
- `503 TBANK_UNAVAILABLE`: экран предлагает повторить загрузку или открыть настройки. - `503 TBANK_UNAVAILABLE`: экран показывает спокойное состояние недоступности; чтение возобновляется автоматически.
- Неизвестная ошибка: экран предлагает только повторить загрузку. - Неизвестная ошибка: то же состояние недоступности без действия повтора.
- Успешный пустой ответ: штатное состояние отсутствия инвестиционных счетов. - Успешный пустой ответ: штатное состояние отсутствия инвестиционных счетов.
Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию. Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию.
+4 -3
View File
@@ -39,10 +39,11 @@
При первой загрузке или новом периоде без сохранённых данных показан При первой загрузке или новом периоде без сохранённых данных показан
каркас страницы. Пустой результат объясняет отсутствие исполненных купонов и каркас страницы. Пустой результат объясняет отсутствие исполненных купонов и
дивидендов, сохраняя фильтры и налоговое пояснение. Ошибка запроса дивидендов, сохраняя фильтры и налоговое пояснение. Ошибка запроса
или нарушение принадлежности выплат даёт локальное состояние с действием «Повторить». или нарушение принадлежности выплат даёт локальное состояние недоступности
без действия повтора; чтение восстанавливается автоматически.
При фоновом обновлении того же периода сохранённый ответ и черновик При фоновом обновлении того же периода сохранённый ответ и черновик
остаются видимыми: загрузку обозначает индикатор, неуспешное обновление — остаются видимыми без индикации обновления и технического предупреждения.
предупреждение с повторной попыткой. Неполнота применённого периода обозначается состоянием самого результата.
На узком экране поля и действия идут вертикально; все карточки выплат На узком экране поля и действия идут вертикально; все карточки выплат
остаются в общем потоке страницы без раскрытия или внутренних областей прокрутки. остаются в общем потоке страницы без раскрытия или внутренних областей прокрутки.
+2 -1
View File
@@ -131,7 +131,8 @@ BondBy переиспользуется зависимостями выпуск
выплат. Наблюдение статуса не запускает повторный импорт. Политика источников выплат. Наблюдение статуса не запускает повторный импорт. Политика источников
и границы — в [модели свежести](../../architecture/cross-cutting/data-freshness.md). и границы — в [модели свежести](../../architecture/cross-cutting/data-freshness.md).
Разделы загружаются независимо, показывают каркас загрузки и локальную повторную попытку. Разделы загружаются независимо, показывают каркас загрузки и локальное
спокойное состояние недоступности без действия повтора.
Ошибка фонового обновления сохраняет последние данные. Смена подключения Ошибка фонового обновления сохраняет последние данные. Смена подключения
сбрасывает поддерево клиентских запросов облигаций вместе с остальными финансовыми запросами; сбрасывает поддерево клиентских запросов облигаций вместе с остальными финансовыми запросами;
выход очищает кэш сессии. Общие график, позиция и личные события выход очищает кэш сессии. Общие график, позиция и личные события
+8 -5
View File
@@ -42,7 +42,8 @@
время получения списка. Изменение условий не заменяет текущий список до время получения списка. Изменение условий не заменяет текущий список до
следующего нажатия «Найти». Повторный поиск получает новый каталог; страницы следующего нажатия «Найти». Повторный поиск получает новый каталог; страницы
одного результата не пересчитываются. Подтверждённый пустой результат отличается одного результата не пересчитываются. Подтверждённый пустой результат отличается
от ошибки источника, для которой интерфейс предлагает повторить поиск. от ошибки источника: она сообщается рядом с полем поиска, а повторить запрос
пользователь может обычным «Найти» — отдельного действия повтора чтения нет.
Ссылка карточки принадлежит выбранному UID; данные разных режимов не смешиваются. Ссылка карточки принадлежит выбранному UID; данные разных режимов не смешиваются.
Прямые ссылки из портфеля сохраняют Прямые ссылки из портфеля сохраняют
@@ -63,8 +64,9 @@
рыночные данные и личная история остаются доступны. рыночные данные и личная история остаются доступны.
Каждый раздел загружается независимо. Первичная загрузка показывает каркас Каждый раздел загружается независимо. Первичная загрузка показывает каркас
раздела, ошибка — локальную повторную попытку. При ошибке фонового обновления раздела, затянувшаяся недоступность — локальное спокойное состояние без
сохраняются предыдущие данные и предупреждение. Цена содержит собственное время повторной попытки. При ошибке фонового обновления сохраняются предыдущие
данные без технического предупреждения. Цена содержит собственное время
UTC, которое может быть старше времени получения ответа. Статус торгов не UTC, которое может быть старше времени получения ответа. Статус торгов не
выводится из свежести цены. Отсутствующее значение не заменяется нулём. выводится из свежести цены. Отсутствующее значение не заменяется нулём.
@@ -166,8 +168,9 @@ TTM означает последние 12 месяцев, MRQ — послед
Закрытые полные свечные интервалы, включая пустые, сохраняются после restart. Закрытые полные свечные интервалы, включая пустые, сохраняются после restart.
Новый день не делает прежнюю незавершённую свечу полной. Новый день не делает прежнюю незавершённую свечу полной.
«Обновить» передаёт текущий инструмент, выбранный график и personal-history Отдельного действия «Обновить» на странице нет: чтение текущего инструмента,
scope. Частичные строки видны с предупреждением; неполные личные оценки выбранного графика и personal-history scope выполняется автоматически.
Частичные строки видны с обозначением неполноты; неполные личные оценки
не становятся нулями. Статус и `sync=observe` не создают новый импорт. не становятся нулями. Статус и `sync=observe` не создают новый импорт.
Смена подключения и известный access loss очищают личное представление; Смена подключения и известный access loss очищают личное представление;
поздние ответы не возвращают прежние данные. Детали — поздние ответы не возвращают прежние данные. Детали —
@@ -12,11 +12,12 @@
величины. Убедитесь, что сравниваемые значения относятся к одному составу величины. Убедитесь, что сравниваемые значения относятся к одному составу
счетов и инструменту. Название или тикер сами по себе не доказывают счетов и инструменту. Название или тикер сами по себе не доказывают
совпадение выпуска; используйте доступные UID, FIGI и ISIN. совпадение выпуска; используйте доступные UID, FIGI и ISIN.
2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и наличие 2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и показанный
предупреждения о клиентском обновлении. Серверный снимок не обещает время исход последней часовой попытки. Серверный снимок не обещает время
каждой котировки, а общий портфель может соединять разные моменты. каждой котировки, а общий портфель может соединять разные моменты.
Если запрос завершился ошибкой, доступное действие «Повторить» должно Действия «Повторить» на экране нет: временный сбой чтения восстанавливается
обновить результат или оставить объяснимую ошибку. Перезагрузка backend автоматически, поэтому расхождение проверяйте по показанному состоянию
источника, а не по результату ручного обновления. Перезагрузка backend
сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости. сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости.
3. **Проверьте состав суммы.** Для одной валюты итог складывается из 3. **Проверьте состав суммы.** Для одной валюты итог складывается из
вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги
+6 -5
View File
@@ -80,11 +80,12 @@ Backend получает данные через серверную
сохраняются предыдущие суммы и время успешного получения в UTC. сохраняются предыдущие суммы и время успешного получения в UTC.
Серверный `isStale` обозначает возраст снимка старше часа: обновлением Серверный `isStale` обозначает возраст снимка старше часа: обновлением
владеет часовой обход, а не запрос экрана. владеет часовой обход, а не запрос экрана. Он показывается рядом с суммами
Клиентское предупреждение означает неудачную попытку обновить уже показанный вместе с временем последнего успешного результата и исходом последней часовой
ответ. Эти состояния независимы и могут появиться одновременно. попытки. Неудачная попытка клиента обновить уже показанный ответ о себе не
Успешный повторный запрос обновляет суммы и время, снимает клиентскую ошибку; сообщает: эти состояния независимы, но пользователю видимо только состояние
серверное предупреждение определяется новым `isStale`. источника. Успешное автоматическое перечитывание обновляет суммы и время;
обозначение устаревания определяется новым `isStale`.
Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md). Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md).
Отдельного чтения истории стоимости нет, но каждый полный снимок Отдельного чтения истории стоимости нет, но каждый полный снимок