spec: reconcile quiet-user-feedback with source-state requirements (#50)
Delta-блоки писались против редакции canonical specs до часовой синхронизации. MODIFIED заменяет требование целиком, поэтому они удаляли бы 10 принятых сценариев при archive. Нормативное поведение зафиксировано по реализации в develop: тихое чтение клиента (frontend 5b30bb8) и видимое состояние источника из meta (frontend 3c226f8, WealthLedger/main#22) совместимы. Запрет относится к индикации собственного чтения и ручному повтору, а не к показу времени, устаревания и исхода часовой попытки.
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
## 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, чтение ограничено прежней политикой, сохранение и удаление требуют явного действия |
|
||||
Reference in New Issue
Block a user