Part of the quiet-user-feedback change; frontend and docs land in their own repositories.
425 lines
41 KiB
Markdown
425 lines
41 KiB
Markdown
## Context
|
||
|
||
Frontend уже разделяет получение данных, page VM и presentation. TanStack
|
||
Query хранит успешные результаты; инвестиционные ресурсы дополнительно
|
||
возвращают `data` и `meta` с полнотой, свежестью и состоянием источника.
|
||
`useResourceQuery` сохраняет общую query identity обычного чтения и наблюдения,
|
||
а `useScreenSync` наблюдает известную работу и предоставляет ручные действия.
|
||
На счетах и портфеле используются `InvestmentRefreshState` и
|
||
`SourceSyncStatus`; сходные элементы встроены в остальные finance views.
|
||
|
||
Сейчас query retries и focus refetch отключены, ошибка status GET прекращает
|
||
наблюдение, а `observe()` также запрещает retry. В банковских формах отдельно
|
||
существуют технические повторы загрузки справочников и перечитывание записи
|
||
после конфликта версий. Это разные случаи: удаление всех обработчиков с именем
|
||
`retry` или `onReload` без сохранения их ответственности недопустимо.
|
||
|
||
Предоставленный frontend OpenAPI уже содержит обычное чтение ресурсов,
|
||
`sync=observe`, status GET и принудительный screen refresh. Дополнительная
|
||
сверка реализации показала: обычное чтение запрашивает missing/stale источники,
|
||
свежие полные источники переиспользуются, существующая running work объединяется.
|
||
Принудительный refresh обходит freshness и охватывает зависимости экрана.
|
||
Поэтому он не подходит для автоматического восстановления обычного чтения.
|
||
|
||
Основные specs текущего store отстают от части реализованной синхронизации.
|
||
Есть пересечение с active change `persist-tbank-data`; его старые UI-требования
|
||
не используются как новая норма поверх согласованных quiet Delta Specs.
|
||
Это различие учитывается в порядке согласования требований ниже.
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- Сосредоточить transport recovery в существующем query layer, а восстановление
|
||
инвестиционной работы — у существующего владельца `useScreenSync`.
|
||
- Упростить presentation interface: view получает готовое содержимое,
|
||
недоступность или необходимое пользовательское действие, а не набор
|
||
технических статусов и callbacks повторной загрузки.
|
||
- Сохранить один финансовый кэш, действующую отмену запросов, границы доступа
|
||
и точные серверные финансовые результаты.
|
||
- Встроить редкие уведомления в существующие Mantine theme и i18n.
|
||
|
||
**Non-Goals:**
|
||
|
||
- Новый query client, offline-хранилище, event bus или универсальный scheduler.
|
||
- Автоматическое повторение мутаций, изменение auth refresh или правил
|
||
разрешения конфликтов пользовательских данных.
|
||
- Новые endpoints, удаление существующего refresh endpoint, изменение DTO,
|
||
серверных TTL, алгоритмов расчёта или схемы хранения.
|
||
- Переработка композиции финансовых страниц и диаграмм.
|
||
|
||
## Decisions
|
||
|
||
### 1. Два владельца восстановления вместо одного глобального retry
|
||
|
||
`shared/api` владеет классификацией transport errors, ограниченным retry и
|
||
deadline application GET. Существующие authenticated query hooks применяют
|
||
общую политику к чтению карт, справочников и статуса подключения. Auth bootstrap
|
||
и пользовательские mutations сохраняют текущую отдельную политику.
|
||
|
||
`useResourceQuery` использует тот же transport recovery, но жизненный цикл
|
||
инвестиционной работы остаётся в `features/tbank-connection`. `useScreenSync`
|
||
становится автоматической orchestration текущего экрана: его callers больше
|
||
не передают `refresh`/`retry` в UI. Page VM продолжает владеть смыслом готовности
|
||
конкретного результата, выбранными параметрами и доступными действиями.
|
||
|
||
Альтернативы: глобальный retry всех запросов и mutations нарушает границы
|
||
записи и доступа; независимые timers каждой страницы размножают политику.
|
||
Собственный универсальный recovery service дублирует Query lifecycle.
|
||
|
||
### 2. Конечная transport policy
|
||
|
||
Для application GET принимаются следующие параметры:
|
||
|
||
| Параметр | Решение |
|
||
| --- | --- |
|
||
| HTTP deadline | 15 секунд на попытку, включая чтение тела ответа |
|
||
| Автоматические повторы | Не более двух после исходного запроса |
|
||
| Паузы | 1 и 2 секунды; серия не перезапускается от render |
|
||
| Временные ошибки | Подтверждённая transport/network failure, timeout, HTTP 408, 500, 502, 503, 504, `TBANK_UNAVAILABLE` |
|
||
| Не повторять | Отмена, ошибки ввода, 401/403/404, известные access/domain failures, 409, неизвестный постоянный исход |
|
||
| Rate limiting | 429 не повторяется короткой серией без доступного корректного `Retry-After`; наличие серверной отсрочки исключает более ранний повтор |
|
||
| Новая серия | Новый актуальный запрос, возвращение сети или возвращение пользователя на экран |
|
||
|
||
Классификация использует существующие `ApiErrorCode` и структурированный
|
||
Problem Details status, с приоритетом отказа доступа над общим HTTP status.
|
||
Отсутствие известного code само по себе не означает временную ошибку.
|
||
Transport failure маркируется там, где выполняется GET: ошибка render,
|
||
`select` или преобразования данных не превращается в network failure.
|
||
Если `Retry-After` используется, его transport metadata сохраняется локально
|
||
в ошибке клиента, без нового поля публичного DTO.
|
||
|
||
Deadline применяется только к application GET, не к auth endpoints и не к
|
||
mutations. Сигнал Query объединяется с сигналом timeout; исходная отмена
|
||
сохраняет причину и не повторяется. Существующий единственный auth refresh
|
||
не дублируется query retry. Восстановление GET после исчерпанного auth refresh
|
||
не допускается до восстановления сессии.
|
||
|
||
Для обычных authenticated reads применяются штатные Query focus/reconnect
|
||
events и текущие entity `staleTime`. На инвестиционных ресурсах обработка этих
|
||
событий координируется `useScreenSync`: параллельный автоматический refetch
|
||
тех же ресурсов из второго владельца выключен. Lifecycle события берутся
|
||
из Query focus/online managers, без второго набора browser listeners.
|
||
|
||
Уход последнего consumer отменяет запрос и его retries через существующий
|
||
AbortSignal. Imperative чтение, начатое screen owner, также прекращается
|
||
при его cleanup, если у запроса не осталось других активных consumers.
|
||
|
||
### 3. Восстановление источника — обычным GET, наблюдение — только observe
|
||
|
||
Для инвестиционного ресурса разделяются три ситуации:
|
||
|
||
| Ситуация | Действие владельца |
|
||
| --- | --- |
|
||
| Не получен HTTP-ответ | Ограниченный transport retry того же режима чтения |
|
||
| Известна running work | Status GET и чтение проекции с `sync=observe` |
|
||
| Необходимые данные missing/stale, а work failed/cancelled либо отсутствует | Ограниченный повтор обычного GET ресурса без `sync=observe` |
|
||
|
||
Успешный HTTP `200` с `data: null` или `sync.state: failed` не считается
|
||
успешным восстановлением источника. Источники определяются из `meta` и
|
||
`meta.dependencies`; `unavailableReason` и предметные ограничения не считаются
|
||
временным сбоем. Неудачная прежняя принудительная сверка уже полного свежего
|
||
результата не запускается заново только ради удаления её внутреннего статуса.
|
||
|
||
После terminal source failure допускаются две повторные обычные загрузки
|
||
нуждающегося в данных ресурса: через 5 и 15 секунд после соответствующего
|
||
неуспеха. Бюджет относится к активации текущей выборки, а не к ID очередной
|
||
серверной попытки. Новый ID работы, успешное чтение failed metadata и повторный
|
||
render не обнуляют бюджет. После его исчерпания следующий запуск возможен
|
||
при новом lifecycle событии восстановления или выборе новой выборки.
|
||
|
||
Transport retry и source recovery не вкладываются бесконтрольно: следующая
|
||
source attempt начинается только после завершения предыдущего GET и его
|
||
transport retries. Для одной серии это не более трёх обычных загрузок ресурса,
|
||
каждая не более чем с тремя HTTP-попытками. Наблюдение running work не является
|
||
новой source attempt и никогда не вызывает принудительный POST refresh.
|
||
|
||
Status observation сохраняет существующий интервал 1 секунда при активном
|
||
экране и известной running work. Повторный tick не отменяет выполняющееся
|
||
чтение или его retry. После terminal status выполняется заключительное
|
||
observe-чтение; если другое чтение ещё идёт, заключительное выполняется после
|
||
него, а не теряется. После ухода, потери сессии или terminal status периодический
|
||
опрос соответствующей работы прекращается. В скрытой вкладке и offline
|
||
новые циклы приостанавливаются; возвращение возобновляет актуальную работу.
|
||
|
||
`404` status ID не означает автоматически потерю доступа ко всем счетам:
|
||
ресурс перечитывается по действующему адресу, где проверяется текущий доступ.
|
||
Подтверждённые access errors ресурса либо статуса подключения сохраняют
|
||
существующую invalidation личных данных. Коды отказа из metadata также не
|
||
должны маскироваться успешным HTTP-ответом.
|
||
|
||
Обычное чтение и observe остаются в одной generated query identity. Внутри
|
||
владельца режим запроса задаётся явно; observe не оставляет постоянный
|
||
observe-only queryFn для последующего обычного восстановления. При чтении
|
||
проверяется фактическое in-flight состояние Query, а не только снимок props.
|
||
Повторный event не создаёт второй запрос того же key. Переход на новую выборку
|
||
отменяет старые timers и не позволяет late response менять новую область.
|
||
|
||
### 4. UI readiness отдельно от query status
|
||
|
||
Page VM выбирает presentation в следующем порядке:
|
||
|
||
1. Подтверждённый access failure: скрытые личные данные и необходимое действие.
|
||
2. Доступный результат текущей выборки: loaded, partial или подтверждённый empty.
|
||
3. Отсутствие результата: первоначальный skeleton либо unavailable.
|
||
|
||
Loaded view не получает `isFetching`, время получения и callbacks технического
|
||
повтора. Технические metadata остаются у data owner для восстановления и
|
||
определения полноты. Не вводится второй кэш успешных финансовых payload.
|
||
|
||
Первичное ожидание области ограничивается 10 секундами. Ошибка завершившегося
|
||
получения или истечение этого срока переводит её в unavailable. Это UI budget,
|
||
а не отмена полезной source work: поздний успешный результат заменяет состояние
|
||
автоматически. Одна общая небольшая presentation-функция/hook в `shared/lib`
|
||
владеет временем ожидания и latch unavailable для identity области. Она
|
||
не выполняет запросы, не знает T-Bank и не копирует финансовые данные.
|
||
Page VM передаёт readiness и identity; смена выборки сбрасывает ожидание,
|
||
очередная попытка той же выборки не вызывает мигание skeleton.
|
||
|
||
Доступность проверяется явно, а не через truthiness суммы. `data: []` и полный
|
||
пустой Portfolio — успешные результаты; `data: null` не равен пустому результату.
|
||
Для неполных totals/comparisons выводится `—` с доступной подписью
|
||
«Итог пока недоступен»/«Сравнение пока недоступно». Для неполного списка
|
||
допустима подпись «Показана часть операций», без статуса синхронизации.
|
||
|
||
Ключи loaded containers не зависят от `isFetching`, revision или времени ответа.
|
||
Фильтры, локальные раскрытия, формы и focus сохраняют существующих владельцев.
|
||
В банковской пагинации прежняя страница не используется как placeholder новой:
|
||
текущий `placeholderData`, допускающий иной page, убирается для этого перехода.
|
||
Сохранение данных при refetch той же страницы обеспечивается самим Query.
|
||
|
||
`InvestmentRefreshState` и `SourceSyncStatus` удаляются вместе с их callers
|
||
и ненужными presentation props. Общие `StateView` и page skeleton переиспользуются;
|
||
нейтральная недоступность не требует нового компонента-обёртки. Ошибки форм,
|
||
валидация и сообщения, объясняющие финансовый смысл, не удаляются поиском строк.
|
||
|
||
### 5. Формы и конфликты сохраняют самостоятельное подтверждение
|
||
|
||
Запросы категорий, исходной покупки возврата и текущей версии записи получают
|
||
обычную политику чтения. Временный refetch не очищает ранее доступные варианты
|
||
и ввод. Когда необходимые данные отсутствуют, соответствующее сохранение
|
||
недоступно; отмена и выход из формы остаются доступны.
|
||
|
||
После `BANK_VERSION_CONFLICT` или аналогичного предметного конфликта существующая
|
||
invalidation автоматически получает актуальную запись. VM использует этот
|
||
результат вместо отдельной кнопки перечитывания. Пользователь по-прежнему
|
||
видит конфликт, сохраняет ввод и явно принимает новую версию перед повторным
|
||
сохранением или удалением. Фоновый ответ не принимает версию за него.
|
||
Пока новая версия неизвестна, повтор опасной записи заблокирован, но форма
|
||
не находится в бесконечном submit и допускает закрытие.
|
||
|
||
Создание и правка карт/операций, удаление, категории и изменение подключения
|
||
сохраняют `retry: false`, version и существующие intent IDs. Общая read policy
|
||
не означает автоматического повторения неуспешной записи.
|
||
|
||
### 6. Mantine Notifications с явным владельцем сообщения
|
||
|
||
Используется `@mantine/notifications` **9.6.1**, согласованный с установленными
|
||
`@mantine/core` и `@mantine/hooks` 9.6.1. Registry подтверждает эту версию и
|
||
peer requirements React/React DOM `^19.2.0`, совместимые с manifest проекта.
|
||
Стили пакета подключаются после Mantine core; один `Notifications` host
|
||
находится в application composition под MantineProvider.
|
||
|
||
Параметры: `limit=2`, `autoClose=4000`, desktop `bottom-right`, narrow viewport
|
||
`bottom-center`; видимая кнопка закрытия имеет русское доступное имя. Сообщение
|
||
не получает отдельный заголовок «Успех» и не переносит focus. Текст принадлежит
|
||
существующему i18n; имя пользователя, token и необработанный error в него не входят.
|
||
Очередь очищается при завершении или смене пользовательской сессии.
|
||
|
||
Уведомление вызывается из одного обработчика успешного действия в его VM,
|
||
не из `useEffect` по `isSuccess` и не из глобального query/mutation interceptor.
|
||
ID относится к выполнению действия, а не к названию операции вообще. Повторное
|
||
завершение того же выполнения подавляется у владельца и получает тот же ID;
|
||
следующее отдельное действие имеет новый ID. Не создаётся постоянный журнал
|
||
показанных сообщений.
|
||
|
||
Решение о необходимости подтверждения принадлежит VM, знающей оставшийся
|
||
экран. Критерий проверяется по фактически видимой выборке, а не по одному
|
||
наличию успешного HTTP-ответа:
|
||
|
||
| Действие и оставшийся контекст | Обратная связь |
|
||
| --- | --- |
|
||
| Созданная/изменённая карта или операция видна в обновлённом списке | Содержимое подтверждает результат, toast отсутствует |
|
||
| Успешная операция не видна из-за периода, страницы, фильтра или другого счёта | Одно «Операция сохранена» после закрытия формы |
|
||
| Удалённая запись исчезла из видимого списка | Toast отсутствует |
|
||
| Запись удалена, но оставшийся экран не отражает её исчезновение | Одно «Операция удалена» |
|
||
| Создание/удаление подключения меняет видимый статус | Toast отсутствует |
|
||
| Замена token при прежнем статусе «Подключён» | Одно «Подключение обновлено» |
|
||
| Категория появилась или изменена в открытом списке/поле | Toast отсутствует |
|
||
| Сохранение успешно, но связанное чтение не подтвердило результат на экране | Краткое подтверждение сохранения без сообщения о сбое чтения |
|
||
|
||
VM получает outcome связанного обновления от существующего владельца
|
||
invalidation; её решение о подтверждении выполняется один раз для action ID.
|
||
Успех записи отделён от успеха последующего GET: ошибка чтения не становится
|
||
ошибкой сохранения и не предлагает повторить уже выполненную запись.
|
||
Получение видимого подтверждения ограничено тем же 10-секундным UI budget;
|
||
после него при отсутствии подтверждения используется одно уведомление.
|
||
Отдельный глобальный наблюдатель mutation results для этого не вводится.
|
||
|
||
Альтернатива `react-toastify` добавляет вторую визуальную систему без нужной
|
||
дополнительной функции. Собственный toast из Mantine `Notification` потребовал
|
||
бы обслуживать очередь, timers и доступность; готовый пакет дешевле.
|
||
|
||
## UI/UX Applicability and Package
|
||
|
||
**Applicability and scope:** `new/changed` — меняются feedback и состояния
|
||
существующих экранов, а не их финансовая композиция.
|
||
|
||
**Package revision:** `quiet-user-feedback-ui-v1`.
|
||
|
||
Пакет полностью inline. Отдельные PNG/SVG не нужны: новая геометрия страниц,
|
||
иллюстрации и визуализация финансовых данных не вводятся. Следующие описания
|
||
и схемы входят в единое Design approval.
|
||
|
||
**Screens, states and interactions:**
|
||
|
||
| Область | Loaded / background failure | Нет результата | Действия |
|
||
| --- | --- | --- | --- |
|
||
| Счета, портфель, режим всех счетов | Заголовок сразу переходит в карточки и содержимое; строки синхронизации отсутствуют | Page-owned skeleton → нейтральная недоступность | Открыть счёт, перейти в раздел, настроить отсутствующее подключение |
|
||
| Операции, доход, события | Фильтры и доступный результат сохраняются; даты событий остаются | Недоступность только текущей выборки или расчёта | Применить/сбросить фильтры, сменить страницу, навигация |
|
||
| Поиск и детали инструментов | Поиск, график и доступные секции без технических controls | Незагруженная секция сохраняет свой заголовок и локальное состояние | Поиск, выбор инструмента и диапазона |
|
||
| Карты, сводки и история | Данные и пользовательские controls сохраняются | Локальная недоступность без ложного нулевого остатка | Добавить/изменить, фильтры, страницы |
|
||
| Настройки подключения | Статус и введённый token сохраняются при временном сбое чтения | Нет формы до первого подтверждённого статуса | Подключить/заменить/отключить после получения статуса |
|
||
| Формы и подтверждения удаления | Локальное ожидание явного действия; ошибки у владельца | Недоступность нужного справочника или актуальной версии | Сохранить/отменить; явное принятие новой версии при конфликте |
|
||
|
||
Схема loaded портфеля:
|
||
|
||
```text
|
||
Счета / Основной счёт
|
||
Основной счёт
|
||
|
||
[ ₽ 125 000,50 ] [ $ 320,00 ]
|
||
Структура портфеля
|
||
Позиции / Деньги / Последние операции / Доход
|
||
```
|
||
|
||
Между заголовком и содержимым нет зарезервированного места под удалённые
|
||
кнопки, даты и баннеры. Temporary refetch failure имеет тот же вид, что loaded.
|
||
|
||
Схема локальной недоступности:
|
||
|
||
```text
|
||
Получено купонами и дивидендами
|
||
Пока недоступно
|
||
|
||
Открыть пассивный доход →
|
||
```
|
||
|
||
Ссылка присутствует только там, где у блока уже есть предметная навигация.
|
||
Нет дополнительного «Попробуйте позже», countdown или обещания завершения.
|
||
Для основной страницы сохраняются shell и существующие пути навигации.
|
||
Нейтральное состояние — читаемый текст, не warning/error Alert. Символ `—`
|
||
для отсутствующей суммы всегда имеет доступное объяснение.
|
||
|
||
Схема редкого подтверждения:
|
||
|
||
```text
|
||
Операция сохранена ×
|
||
```
|
||
|
||
Нотификация объявляется ненавязчиво, не закрывает modal, не переводит фокус
|
||
и не является единственным носителем ошибки или необходимого действия.
|
||
При отображении над формой учитывается её focus trap; закрытие уведомления
|
||
не отбирает доступ к кнопкам формы. Повторные фоновые попытки не создают
|
||
live-announcements. Skeleton busy остаётся только у реально ожидающей области.
|
||
|
||
**Responsive behavior:** существующие layout и semantic tokens сохраняются.
|
||
При ширине 320–767 px нейтральные состояния занимают ширину области; toast
|
||
располагается снизу по центру с отступами и учётом safe area, без горизонтального
|
||
скролла. От 768 px — справа снизу. Сообщения переносятся, кнопка закрытия
|
||
остаётся доступной. Контейнер уведомлений не блокирует pointer events вне
|
||
самих сообщений и не перекрывает единственный путь к основным controls.
|
||
Появление учитывает reduced motion.
|
||
|
||
**Reused source and differences:** формального reuse чужого SDD UI package нет.
|
||
Применяются текущие Mantine theme, `StateView` и page-owned skeleton; их
|
||
ответственность сохраняется. Отдельная новая theme или макет неизменяемых
|
||
финансовых секций не создаются.
|
||
|
||
## Delivery Map
|
||
|
||
| Area | Status | Architectural outcome or N/A reason |
|
||
| --- | --- | --- |
|
||
| frontend | `affected` | Query recovery, автоматическое наблюдение ресурсов, упрощённые VM/views, сохранение form conflict flow, Mantine Notifications, согласованные UI/data conventions и каталог состояний |
|
||
| backend | `N/A` | Существующие обычные GET, observe и server-side work deduplication достаточны; DTO, force endpoint, TTL, хранение и серверные повторы не меняются |
|
||
| specs | `affected` | Согласованные Delta Specs заменяют старые UI/retry obligations; новое правило feedback входит в canonical specs и индекс; пересечение с persist lifecycle разрешается без переписывания чужого active change |
|
||
| docs | `affected` | As-built объяснение тихой загрузки, восстановления и уведомлений заменяет описание ручного refresh в UI resilience, data freshness и затронутых предметных страницах; принятое сквозное решение фиксируется в журнале ADR |
|
||
|
||
## Solution Assessment
|
||
|
||
Обязательный trigger — изменение общей политики caching/recovery и
|
||
координации concurrent reads; дополнительно выбирается общий механизм уведомлений.
|
||
|
||
| Вариант | Fit и зрелость | Security и ограничения | Operating / migration cost | Решение |
|
||
| --- | --- | --- | --- | --- |
|
||
| Существующий TanStack Query + текущие resource/screen owners | Уже владеет cache, retry, cancellation, focus/online events; domain metadata остаётся отдельной | Сохраняет session cancellation; требует отличать read failure от failed work и запретить retries mutations | Без новой runtime-инфраструктуры; изменения сосредоточены в существующих owners | Выбран |
|
||
| Новый scheduler или state-machine runtime | Потребовал бы повторить текущий query lifecycle | Добавляет второе состояние и риск late-response/cache рассогласования | Новая dependency и миграция всех consumers без текущей необходимости | Отклонён |
|
||
| Периодический force refresh | Не соответствует ограниченному recovery текущих missing/stale данных | Повторяет внешнюю работу и игнорирует freshness | Лишние RPC и нагрузка; усложняет rate limiting | Отклонён |
|
||
| Mantine Notifications 9.6.1 | Зрелый companion текущей UI-системы; очередь, lifecycle, responsive position | Только доверенные локализованные тексты, без HTML/error payload | Один согласованный пакет; нет нового сервиса или отдельной темы | Выбран |
|
||
| React-Toastify | Зрелый самостоятельный механизм, но дополнительная функция не нужна | Потребует отдельно согласовывать styling и accessibility | Вторая UI-интеграция и её поддержка | Отклонён |
|
||
| Собственная очередь над Mantine Notification | Встроенный primitive есть, готовой очереди в core нет | Собственные timers, focus/live-region и cleanup повышают риск | Собственный код дороже готового companion | Отклонён |
|
||
|
||
Выбор опирается на текущие исходники и публичную документацию
|
||
[Query retries](https://tanstack.com/query/latest/docs/framework/react/guides/query-retries),
|
||
[Query cancellation](https://tanstack.com/query/latest/docs/framework/react/guides/query-cancellation),
|
||
[focus refetch](https://tanstack.com/query/latest/docs/framework/react/guides/window-focus-refetching)
|
||
и [Mantine Notifications](https://mantine.dev/x/notifications/).
|
||
Документация объясняет возможности библиотек; domain recovery и UI-пакет
|
||
полностью определены здесь и не зависят от доступа к этим ссылкам.
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **Тишина не означает актуальность в реальном времени.** TTL сам не создаёт
|
||
расписание; после исчерпания recovery budget новая серия ждёт lifecycle
|
||
event. Это ограничивает нагрузку ценой возможного ожидания следующего
|
||
возвращения на экран при долгой недоступности источника.
|
||
- **Короткий UI budget и долгая история.** Через 10 секунд может появиться
|
||
«Пока недоступно», хотя работа продолжается. Готовый результат заменяет
|
||
это состояние; уже доступные области не ждут чужой истории.
|
||
- **Умножение повторов.** Transport, status polling и source recovery имеют
|
||
разных владельцев и конечные бюджеты. Новые source IDs и failed metadata
|
||
не запускают неограниченную серию.
|
||
- **Access loss важнее cached content.** Ошибки доступа проверяются до loaded
|
||
presentation. Status ID not found не смешивается с account not found.
|
||
- **Конфликт версии не является ошибкой чтения.** Автоматическое получение
|
||
новой версии не подтверждает её принятие и не повторяет запись.
|
||
- **Уведомления могут дублировать содержимое.** Решение остаётся у action VM,
|
||
где известна видимая выборка; глобальный toast на каждый success исключён.
|
||
- **Исходные ветки не равны новой canonical норме.** Существующие local
|
||
изменения переноса PortfolioStructure сохраняются; design не требует их
|
||
отката. Удалённая актуальность specs ранее не была подтверждена из-за DNS;
|
||
фактическая база поставки требует сверки перед её объединением.
|
||
|
||
## Migration Plan
|
||
|
||
1. Сохранить текущую canonical базу quiet change и порядок требований:
|
||
утверждённое quiet-поведение заменяет старые обязательства показывать
|
||
статусы и ручной retry. Технические возможности уже предоставленного API
|
||
используются без автоматического переноса всех artifacts `persist-tbank-data`.
|
||
Его последующий sync/archive не должен восстанавливать отменённые UI-правила:
|
||
пересекающийся change требует отдельного rebase и approval до своего archive.
|
||
Если он попадает в canonical specs раньше quiet, quiet deltas адаптируются
|
||
к новой базе с сохранением согласованного поведения и повторным approval
|
||
затронутых artifacts. Чужие active artifacts сейчас не переписываются.
|
||
2. Ввести recovery у существующих владельцев чтения и отделить presentation
|
||
readiness от transport status. Сохранить текущие auth/access/invalidation
|
||
semantics и различие обычного чтения и observe.
|
||
3. Перевести экраны и формы на тихую presentation, убрать ручные технические
|
||
controls и сохранить предметные conflict actions. Подключить единый host
|
||
уведомлений и явные action outcomes. Эти части выпускаются одним frontend
|
||
изменением: удаление recovery-кнопок не поставляется отдельно от замены.
|
||
4. Согласовать новые conventions и presentation states, затем обновить
|
||
as-built docs и canonical specs по принятому lifecycle. Серверный rollout
|
||
и миграция данных для этого решения не требуются.
|
||
|
||
### Rollback Contract
|
||
|
||
| Field | Decision |
|
||
| --- | --- |
|
||
| Status | `required` |
|
||
| Reason | Recovery касается сохранения личных снимков, access invalidation и взаимодействия с финансовыми mutations |
|
||
| Trigger | Новая политика показывает данные прежней границы, создаёт неконтролируемые запросы, повторяет запись или ломает восстановление без ручных controls |
|
||
| Actions | Вернуть предыдущую frontend-поставку целиком: старые query policies вместе с прежними UI recovery controls; не откатывать только автоматическое восстановление под уже тихим UI |
|
||
| Data safety | Не менять БД, серверные данные, connection revision, intent IDs и versions; не повторять пользовательские mutations для компенсации; при подозрении на неправильный показ завершить локальную сессию и очистить её кэш |
|
||
| Verification | После отката данные разделены по сессии и подключению, временный сбой имеет прежний доступный recovery path, чтение ограничено прежней политикой, сохранение и удаление требуют явного действия |
|