spec: reconcile quiet-user-feedback with source-state requirements (#50)
Specs security / security (push) Successful in 9s
Specs verify / verify (push) Successful in 10s

Переносит change quiet-user-feedback в develop и согласует его delta-блоки с текущими canonical specs. Нормативное поведение: тихое чтение клиента и видимое состояние источника из meta.
This commit was merged in pull request #51.
This commit is contained in:
2026-10-17 21:46:58 +03:00
parent 06d08e2972
commit 5a7e319caf
14 changed files with 1454 additions and 0 deletions
@@ -0,0 +1,2 @@
schema: wealth-ledger-sdd
created: 2026-10-04
@@ -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, чтение ограничено прежней политикой, сохранение и удаление требуют явного действия |
@@ -0,0 +1,102 @@
## Why
Пользователь WealthLedger хочет видеть свои финансы, а не обслуживать получение
данных. Сейчас сообщения «Источник не удалось обновить», «Сохранённые данные
не изменены», время последней загрузки и действия «Обновить»/«Повторить»
перекладывают на него контроль технической работы приложения. Эти элементы
повторяются на разных экранах и отвлекают от содержимого.
Простого удаления надписей недостаточно: восстановление после временного сбоя
не должно зависеть от исчезнувшей кнопки. Нужен единый принцип:
тихое чтение и обновление, самостоятельное восстановление и понятный результат
явных действий пользователя.
## What Changes
- На экранах просмотра приложение сохраняет доступные данные при фоновом
обновлении и временном сбое, не показывает технические предупреждения,
индикаторы фоновой работы, отметки времени получения и элементы ручного
обновления или повторной загрузки. Это относится к счетам, портфелю, операциям,
доходам, событиям, инструментам, поиску, картам и чтению состояния подключения.
Те же сообщения не переносятся во всплывающие уведомления или подсказки.
- После временных сбоев приложение самостоятельно возвращается к нормальной
работе, не требуя от пользователя повторять загрузку или перезагружать страницу.
- Первичное ожидание сохраняет понятную структуру будущего содержимого. При
затянувшейся недоступности вместо бесконечной загрузки показывается спокойное
локальное состояние «Пока недоступно» без технического повтора. После
восстановления содержимое появляется автоматически. Сбой одного блока не
скрывает остальные доступные блоки.
- Отсутствие данных, подтверждённая пустота и неполный финансовый результат
остаются различимыми. Неизвестные суммы не становятся нулями, незавершённый
расчёт не выдаётся за окончательный, а незагруженный список — за отсутствие
операций. Неполнота передаётся состоянием конкретного результата без отчёта
о процессе синхронизации. Даты операций, выплат и выбранных периодов сохраняются.
- Редкие краткие уведомления подтверждают завершённые действия пользователя,
когда результат иначе легко пропустить. Очевидные изменения экрана не
сопровождаются дублирующим уведомлением. Фоновая загрузка не порождает
уведомлений об успехе или ошибке, а повтор одного события — каскад сообщений.
- Ошибки ввода и неуспешное сохранение остаются у соответствующего поля или
формы с сохранением ввода. Действия, необходимые от пользователя, например
вход или восстановление подключения, остаются понятными и доступными.
Исчезающее уведомление не является единственным сообщением о таком препятствии.
- Обновление данных не сбрасывает выбранные счёт, инструмент, период, фильтры
и не прерывает работу пользователя. Личные данные остаются доступны только
тому, кто имеет право их просматривать.
Ожидаемый результат: пользователь видит устойчивое финансовое содержимое,
не управляет загрузкой и получает обратную связь только там, где она помогает
понять результат его действия или необходимый следующий шаг.
Non-goals:
- Скрывать неуспешные изменения данных или выдавать их за сохранённые.
- Менять финансовые расчёты, валютный смысл, даты событий и правила доступа.
- Добавлять центр уведомлений, историю сообщений или уведомления вне приложения.
- Обещать актуальность данных в реальном времени.
## Capabilities
### New Capabilities
- `specs/frontend-user-feedback/spec.md`: единая политика редких уведомлений
о действиях пользователя, локальных ошибок и отсутствия технического шума
на всех экранах приложения, включая карты и инструменты.
### Modified Capabilities
- `specs/frontend-data-resilience/spec.md`: тихое сохранение доступного
результата и самостоятельное восстановление вместо предупреждений
и ручного повтора; защита границ личных данных сохраняется.
- `specs/frontend-loading-presentation/spec.md`: отсутствие индикации фонового
обновления при доступных данных и переход от первичного ожидания к устойчивому
локальному состоянию недоступности; явные действия сохраняют обратную связь.
- `specs/frontend-ui-resilience/spec.md`: ожидаемые состояния не требуют
предупреждения об устаревании и ручного повтора; навигация после
аварийной ошибки и действия восстановления доступа остаются доступными.
- `specs/brokerage-accounts/spec.md`: тихое отображение сохранённого списка,
автоматическое восстановление временных ошибок и отсутствие ручной загрузки.
- `specs/portfolio-view/spec.md`: показ портфеля без времени получения,
предупреждений об обновлении и кнопок повтора, с автоматическим восстановлением
и сохранением корректного смысла доступных финансовых значений.
- `specs/brokerage-account-ledger/spec.md`: локальное тихое восстановление
последних операций и журнала без технических действий пользователя.
- `specs/brokerage-account-upcoming-events/spec.md`: сохранение доступных событий
при временной ошибке и локальное автоматическое восстановление вместо ручного
повтора и технической ошибки блока.
- `specs/brokerage-account-income-analytics/spec.md`: локальная недоступность
незавершённого расчёта и автоматическое восстановление дохода и его сводки
без выдачи неполного результата за окончательный.
- `specs/tbank-connection/spec.md`: автоматическое восстановление чтения статуса
без кнопки повтора; ошибки создания, замены и удаления подключения сохраняют
явную обратную связь и введённые данные.
## Impact
Изменение охватывает просмотр счетов, портфеля, операций, доходов, будущих
событий, инструментов и карт, поиск и настройки подключения.
Пользователю больше не нужно следить за обновлением данных и устранять
временные сбои загрузки вручную. Интерфейс становится спокойнее, а сообщения
помогают понять результат собственного действия или необходимый следующий шаг.
Финансовый смысл данных, понятность ошибок сохранения и защита личной
информации сохраняются.
@@ -0,0 +1,82 @@
## MODIFIED Requirements
### Requirement: Realized analytics never presents an incomplete result as complete
Система MUST формировать итог выбранного периода, его trend buckets,
instrument breakdown и drill-down только после успешного получения всех
необходимых cursor pages исполненных операций этого периода. Каждое comparison
MUST дополнительно требовать полного результата соответствующего периода
сравнения. Ошибка любой необходимой страницы MUST NOT возвращать частичные
totals, comparisons, buckets или
breakdown как полный результат. При временной недоступности клиент MUST
сохранять предыдущий полный результат той же выборки либо показывать
нейтральную недоступность соответствующего расчёта без технических сообщений
и ручного повтора. Восстановление SHALL выполняться автоматически по
`frontend-data-resilience`.
Доступные независимые части MUST оставаться видимыми; неполное сравнение MUST
обозначаться как недоступное без нулевой базы и окончательной разницы. Успешный
полный период без подходящих выплат MUST возвращать нулевой результат и
отображаться с объяснением пустого состояния. Неизвестный результат MUST NOT
подменяться таким нулевым результатом.
#### Scenario: Все страницы операций успешно получены
- **WHEN** для расчёта требуется несколько cursor pages и каждая страница успешно получена
- **THEN** система формирует результат по всем подходящим Ledger Entries со всех страниц
#### Scenario: Одна из страниц операций недоступна
- **WHEN** получение любой необходимой cursor page завершается временной ошибкой
- **THEN** страница сохраняет предыдущий полный результат той же выборки либо показывает нейтральную недоступность расчёта без ручного повтора и не выдаёт частичный результат за полный
#### Scenario: В периоде нет купонов и дивидендов
- **WHEN** все необходимые операции успешно получены, но среди них нет подходящих coupon или dividend Ledger Entries
- **THEN** страница показывает нулевой результат и объясняет, что за выбранный период купоны и дивиденды не найдены
#### Scenario: Основной период готов, сравнение пока нет
- **WHEN** выбранный период полностью рассчитан, а необходимый период сравнения ещё не получен
- **THEN** основной доход остаётся доступным, сравнение обозначается как недоступное без нулевой базы, частичной разницы и сообщения о синхронизации
### Requirement: Account overview has an independent current-month income summary
Обзор выбранного Brokerage Account MUST показывать компактный source-currency
summary «Получено купонами и дивидендами» за период от первого дня текущего
месяца по Europe/Moscow до сегодняшнего дня включительно и действие перехода
на страницу «Пассивный доход». Summary MUST всегда использовать этот current-month
период и MUST NOT наследовать последний пользовательский период страницы аналитики.
Пустой или неуспешный расчёт MUST оставаться локальным состоянием блока и
MUST NOT заменять успешно отображённый Portfolio или другие блоки обзора.
Временная ошибка обновления MUST сохранять доступную сводку той же выборки без
предупреждения и ручного повтора. При отсутствии результата SHALL показываться
нейтральная недоступность после первичного ожидания с автоматическим восстановлением.
#### Scenario: Обзор показывает доход текущего месяца
- **WHEN** обзор Brokerage Account загружен и current-month analytics успешно рассчитана
- **THEN** он показывает отдельные source-currency итоги с начала текущего месяца по сегодняшний день и переход к полной аналитике
#### Scenario: Пользовательский период не влияет на обзор
- **WHEN** User ранее применил на странице аналитики период, отличный от текущего месяца
- **THEN** summary обзора по-прежнему рассчитывается от первого дня текущего месяца по сегодняшний день
#### Scenario: Current-month summary недоступен
- **WHEN** Portfolio обзора успешно загружен, но расчёт current-month summary завершается временной ошибкой
- **THEN** Portfolio и остальные блоки остаются доступными, а summary сохраняет предыдущий результат той же выборки либо показывает нейтральную недоступность без ручного повтора
## ADDED Requirements
### Requirement: Доход обновляется без технических сообщений
Страница дохода MUST сохранять применённый период, черновик фильтров и
доступный результат при фоновом обновлении. Временная ошибка SHALL
восстанавливаться автоматически без кнопок обновления и повтора, времени
получения данных и уведомлений об их загрузке. Изменение периода MUST NOT
подписывать прежний результат новыми датами. Ошибки пользовательского периода
MUST оставаться у полей и MUST NOT запускать автоматический повтор ошибочного
ввода. Подтверждённая потеря доступа MUST скрывать личные результаты.
#### Scenario: Доход обновляется при редактировании периода
- **WHEN** пользователь редактирует черновик дат, а фоновое обновление применённого периода завершилось
- **THEN** черновик и фокус сохраняются, результат относится к прежнему применённому периоду и техническое уведомление не появляется
#### Scenario: Новый период пока недоступен
- **WHEN** пользователь применил другой допустимый период, но его расчёт ещё не получен
- **THEN** страница показывает ожидание или недоступность нового результата, не выдаёт прежние суммы за доход нового периода и восстанавливается автоматически
@@ -0,0 +1,46 @@
## MODIFIED Requirements
### Requirement: Account overview includes recent ledger events
Обзор счёта MUST показывать пять последних исполненных записей журнала этого
счёта и действие перехода к его полному журналу. Каждая запись MUST иметь ту же
визуальную идентичность инструмента и семантику денежного движения, что и запись
полного журнала. Неуспешный или пустой запрос MUST оставаться локальным состоянием
блока и MUST NOT заменять успешно отображённый Portfolio. При временном сбое
обновления блок MUST сохранять доступные записи без технического предупреждения
или ручного повтора. При отсутствии результата он SHALL показывать первичное
ожидание либо нейтральную недоступность, автоматически восстанавливая чтение.
Неподтверждённая пустота MUST NOT представляться отсутствием операций.
#### Scenario: Отображение последних событий
- **WHEN** загружается обзор счёта и его журнал содержит исполненные операции
- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал
#### Scenario: Последние записи недоступны
- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается временной ошибкой
- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние записи либо показывает нейтральную недоступность без технического повтора и восстанавливается автоматически
#### Scenario: Последняя запись не содержит visual metadata инструмента
- **WHEN** одна из последних записей не имеет доступной presentation metadata инструмента
- **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения
## ADDED Requirements
### Requirement: Журнал восстанавливает чтение без ручного обновления
Страница журнала SHALL сохранять записи текущей выборки при временном сбое
обновления и восстанавливать чтение автоматически по `frontend-data-resilience`.
Период, категории и выбранная страница MUST сохраняться; результат другой
выборки MUST NOT подменять текущую. Страница MUST NOT показывать время получения,
технические статусы или действия ручного повтора. Даты операций, фильтры,
действия их применения и переходы по страницам MUST оставаться доступными.
При отсутствии результата SHALL применяться первичное ожидание и нейтральная
недоступность, а не сообщение об отсутствии операций до подтверждения полноты.
#### Scenario: Обновление журнала временно не удалось
- **WHEN** для применённого периода и страницы уже есть записи, а обновление завершается временной ошибкой
- **THEN** записи и параметры просмотра сохраняются без предупреждения и кнопки повтора до автоматического восстановления
#### Scenario: Следующая страница ещё не получена
- **WHEN** пользователь перешёл к следующей странице, но её загрузка временно не удалась
- **THEN** область новой страницы показывает ожидание или нейтральную недоступность, не выдаёт прежние записи за новую страницу и восстанавливает чтение автоматически
@@ -0,0 +1,73 @@
## MODIFIED Requirements
### Requirement: Account page presents an upcoming events list
Страница Brokerage Account MUST предоставлять действие перехода на отдельную
страницу «Будущие события». Эта страница MUST показывать все полученные события
компактным списком без фильтров, пагинации и суммарного итога, с датой, типом,
инструментом и прогнозной суммой в исходной валюте, если она известна. Страница
MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может
быть неполным из-за доступности данных источника. Это предметное ограничение
прогноза MUST NOT превращаться в отчёт о технической синхронизации.
При временном сбое обновления страница MUST сохранять ранее доступный результат
той же выборки с его исходными `calculatedAt` и окном, без индикации
собственного чтения и ручного повтора. Состояние источника из `meta`, включая
неуспех последней часовой попытки и неполноту, MUST представляться по
`frontend-data-resilience`. При отсутствии результата SHALL показываться
первичное ожидание либо нейтральная недоступность с автоматическим
восстановлением по `frontend-data-resilience`. Подтверждённая потеря доступа
MUST скрывать личные данные независимо от наличия сохранённого результата.
#### Scenario: Просмотр ближайших событий
- **WHEN** User открывает «Будущие события» из обзора Brokerage Account
- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы
#### Scenario: Пустое состояние страницы
- **WHEN** запрос будущих событий успешно возвращает полный пустой список
- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника
#### Scenario: Ошибка страницы
- **WHEN** запрос будущих событий завершается временной ошибкой
- **THEN** страница сохраняет доступный результат той же выборки либо показывает нейтральную недоступность без ручного повтора, восстанавливая чтение автоматически и не выдавая частичные данные за полный прогноз
#### Scenario: Прогнозный дивиденд с неизвестной суммой
- **WHEN** прогноз содержит дивиденд с известной датой реестра, но без суммы
- **THEN** его карточка обозначает дату как дату реестра и сумму как неизвестную, не показывает нулевую или полученную выплату
#### Scenario: Ошибка обновления при сохранённом прогнозе на странице
- **WHEN** запрос будущих событий возвращает последний полный прогноз вместе с ошибкой его обновления
- **THEN** страница показывает этот прогноз с его исходными `calculatedAt` и окном и отдельно обозначает неуспех обновления источника, не предлагая ручной повтор
#### Scenario: Первая загрузка либо ошибка без сохранённого прогноза
- **WHEN** запрос будущих событий возвращает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех
- **THEN** страница показывает доступное состояние первичного ожидания либо нейтральной недоступности без частичного списка и восстанавливает чтение автоматически
### Requirement: Account overview includes upcoming events
Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash
Event и действие перехода на полную страницу «Будущие события». Неуспешный
или пустой запрос этого блока MUST оставаться локальным состоянием блока и
не заменять успешно отображённый Portfolio. При временном сбое обновления
блок MUST сохранять доступные события сохранённого окна без индикации
собственного чтения; при отсутствии результата SHALL показываться нейтральная
недоступность после первичного ожидания. Состояние источника из `meta` MUST
представляться по `frontend-data-resilience`. Восстановление MUST выполняться
автоматически без ручного повтора и без подмены неизвестного результата
подтверждённой пустотой.
#### Scenario: Отображение ближайших событий
- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события
- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице
#### Scenario: Ближайшие события недоступны
- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается временной ошибкой
- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние события либо показывает нейтральную недоступность без технической ошибки и ручного повтора
#### Scenario: Ближайшие события недоступны без сохранённого прогноза
- **WHEN** обзор счёта успешно загружен, а события возвращают `data: null` и `meta` обозначает выполнение первой загрузки либо её неуспех
- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственное доступное состояние первичного ожидания либо нейтральной недоступности без частичного списка
#### Scenario: Неуспешное обновление не скрывает сохранённый прогноз
- **WHEN** обзор счёта получает сохранённый полный прогноз вместе с ошибкой его обновления
- **THEN** Portfolio и события сохранённого окна остаются доступными, а блок отдельно обозначает неуспех обновления источника, не предлагая ручной повтор
@@ -0,0 +1,116 @@
## MODIFIED Requirements
### Requirement: Экран списка брокерских счетов
Клиент SHALL загружать список при открытии защищённого маршрута `/accounts`
без ручного обновления. Клиент MUST использовать успешный frontend-кэш списка
в течение 60 секунд. После истечения этого срока открытие или возвращение
на экран SHALL инициировать получение актуального списка; имеющийся Client
Cached Snapshot MUST оставаться видимым во время обновления и после временной
ошибки без индикации собственного чтения и действий повтора.
Автоматическое восстановление SHALL соответствовать `frontend-data-resilience`.
Открытый экран MAY периодически перечитывать backend-результат, сохраняя
видимый snapshot, и MUST NOT создавать импорт T-Bank. Состояние источника из
`meta` MUST представляться по `frontend-data-resilience`.
При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный
список широких интерактивных карточек счетов с локализованными типом и, если
доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип,
имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта,
вести на `/accounts/:accountId` по всей области и сохранять доступное
клавиатурное состояние. После успешной замены или удаления T-Bank connection
клиент MUST инвалидировать список счетов.
Для доступной `portfolioSummary` карточка MUST показывать не более двух
Money-итогов в исходных валютах и обозначать наличие остальных валют, если
они есть; она MUST NOT показывать или подразумевать FX-конвертацию,
межвалютный агрегированный итог либо Cash Balance как отдельный второй итог.
При `portfolioSummary: null` карточка MUST показывать локальное нейтральное
состояние недоступности сводки, сохраняя метаданные счёта и возможность открыть
его. При доступной пустой сводке карточка MUST не выдавать отсутствие итогов
за временную ошибку.
#### Scenario: Пользователь открывает счёт из списка
- **WHEN** клиент успешно загрузил непустой список и User выбирает карточку счёта
- **THEN** клиент переходит на `/accounts/:accountId` с `id` выбранного счёта
#### Scenario: Список пуст
- **WHEN** клиент получает успешный полный пустой массив
- **THEN** клиент показывает штатное сообщение об отсутствии инвестиционных счетов T-Bank
#### Scenario: Список берётся из свежего frontend-кэша
- **WHEN** User повторно открывает список в течение 60 секунд после успешной загрузки
- **THEN** клиент показывает сохранённые счета без нового API-запроса
#### Scenario: Устаревший список загружается повторно
- **WHEN** User открывает список после истечения 60 секунд frontend-кэша и уже существует последний успешный список
- **THEN** клиент оставляет этот список видимым во время нового запроса без индикации фонового обновления
#### Scenario: Открытый экран перечитывает список
- **WHEN** экран Accounts остаётся открытым 30 минут после последнего успешного HTTP-чтения
- **THEN** клиент выполняет фоновое HTTP-перечитывание backend-результата, сохраняет видимый snapshot и не запускает импорт T-Bank
#### Scenario: Последняя попытка неуспешна после полного списка
- **WHEN** клиент получает полный `data` списка и `meta` с неуспехом последней часовой попытки
- **THEN** клиент сохраняет список видимым, показывает его исходное время и текстовое либо доступно именованное предупреждение о неуспешной попытке, не предлагая ручной повтор
#### Scenario: Первая загрузка списка выполняется или неуспешна
- **WHEN** клиент получает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех
- **THEN** клиент показывает соответственно доступное состояние загрузки либо локальной недоступности без карточек счетов и без представления частичного списка как полного
#### Scenario: Ошибка фонового обновления не скрывает snapshot
- **WHEN** обновление устаревшего списка завершается временной ошибкой при наличии последнего успешного Client Cached Snapshot
- **THEN** клиент сохраняет snapshot видимым без предупреждения и ручного повтора и восстанавливает получение данных автоматически
#### Scenario: Карточка ограничивает валютные итоги
- **WHEN** доступная сводка счёта содержит итоги в трёх или более валютах
- **THEN** карточка показывает не более двух исходных валютных итогов и обозначает наличие остальных без общего сконвертированного значения
#### Scenario: Недоступность сводки локальна карточке
- **WHEN** клиент получает список, в котором `portfolioSummary` одного счёта равна `null`
- **THEN** он показывает нейтральную недоступность только этой сводки, не скрывает остальные карточки и сохраняет переход на детали счёта
#### Scenario: Пустая сводка не является ошибкой
- **WHEN** клиент получает доступную `portfolioSummary` с пустым `totalsByCurrency`
- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной ошибки
### Requirement: Клиентские состояния ошибки списка счетов
Клиент SHALL автоматически восстанавливать временно неуспешное чтение списка
по правилам `frontend-data-resilience` без кнопок обновления и повтора. При
`TBANK_NOT_CONNECTED` клиент MUST показать необходимость подключения и действие
открытия настроек T-Bank. При `INVALID_TBANK_TOKEN` клиент MUST показать
необходимость восстановить подключение и то же действие. Эти подтверждённые
состояния MUST скрывать личный список и MUST NOT запускать автоматические повторы.
При временной недоступности, включая `TBANK_UNAVAILABLE`, клиент MUST сохранять
успешный результат текущей границы доступа без предупреждений. Если результата
нет, после первичного ожидания клиент SHALL показать спокойную локальную
недоступность без технических подробностей. Неизвестная ошибка MUST получать
безопасное локальное представление; отсутствие известного кода MUST NOT
разрешать бесконечные повторы или обход доступа. Временный сбой MUST NOT
предлагать перенастройку действующего подключения как способ повторить загрузку.
#### Scenario: Подключение требуется
- **WHEN** клиент получает `TBANK_NOT_CONNECTED`
- **THEN** клиент не показывает список и предлагает открыть настройки T-Bank
#### Scenario: Известная ошибка не повторяется автоматически
- **WHEN** запрос списка завершается подтверждённой ошибкой подключения или доступа, требующей действия пользователя
- **THEN** клиент не выполняет автоматических повторов и предлагает соответствующее действие вместо технической повторной загрузки
#### Scenario: Token требует внимания
- **WHEN** клиент получает `INVALID_TBANK_TOKEN`
- **THEN** клиент скрывает личный список и предлагает восстановить подключение в настройках T-Bank
#### Scenario: Временная ошибка T-Bank
- **WHEN** клиент получает `TBANK_UNAVAILABLE` без доступного результата списка
- **THEN** клиент автоматически восстанавливает получение и после первичного ожидания показывает нейтральную недоступность без повторной загрузки и предложения перенастроить подключение
#### Scenario: Неизвестная ошибка загрузки
- **WHEN** клиент получает ошибку без известного кода списка счетов и без доступного результата
- **THEN** клиент показывает безопасное нейтральное состояние без технического сообщения или кнопки повтора, применяя только допустимое ограниченное восстановление
#### Scenario: Известная ошибка при сохранённом snapshot
- **WHEN** фоновое обновление списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE`
- **THEN** клиент сохраняет snapshot видимым без предупреждения, времени получения и ручных действий восстановления
@@ -0,0 +1,138 @@
## MODIFIED Requirements
### Requirement: Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio
Frontend MUST сохранять последний успешный результат списка Brokerage Accounts,
Portfolio и других экранов чтения при фоновом обновлении и временной ошибке
для того же пользователя, подключения и набора данных. Подтверждённый пустой
результат MUST также считаться успешным. Смена счёта, инструмента, поискового
запроса, страницы или применённого периода MUST NOT представлять данные прежней
выборки как результат новой.
Доступный результат MUST оставаться видимым без индикаторов фонового чтения
клиента, технических предупреждений об этом чтении и действий ручного повтора.
Frontend MUST NOT подменять внутренние признаки свежести и полноты, успешное
время источника или ошибку обновления только ради отсутствия их отображения.
Client Cached Snapshot MUST оставаться отличным от server-side fallback;
ошибка чтения MUST NOT изменять `Portfolio.isStale`.
Состояние источника из `meta` — время последнего успешного результата, признак
устаревания, исход последней часовой попытки и неполнота — MUST представляться
текстом либо доступно именованным элементом, а не только цветом, и MUST
различать отсутствие попытки, выполнение, полный успех, частичный успех и
неуспех. Это состояние принадлежит источнику данных, а не собственному чтению
клиента, и MUST NOT требовать от пользователя действия.
Обновление MUST сохранять выбранные фильтры, период, открытые разделы, ввод,
фокус и положение просмотра, пока соответствующее содержимое существует.
При logout, смене подключения или подтверждённом отказе доступа личные данные
прежней границы MUST перестать отображаться; поздний результат прежней загрузки
MUST NOT возвращать их на экран.
#### Scenario: Список Brokerage Accounts обновляется в фоне
- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается фоновое обновление того же списка
- **THEN** frontend оставляет список видимым без индикации собственного чтения и технических действий
#### Scenario: Resource сохраняется после неуспешной часовой попытки
- **WHEN** response содержит полный `data` и `meta`, обозначающий неуспех последней часовой попытки
- **THEN** frontend сохраняет `data` и его исходное время видимыми и отдельно обозначает неуспех попытки, не предлагая ручной повтор
#### Scenario: Обновление Portfolio завершается ошибкой после успешного ответа
- **WHEN** экран Brokerage Account уже показывает Client Cached Snapshot Portfolio, а последующее обновление этого же Portfolio завершается временной ошибкой
- **THEN** frontend сохраняет Portfolio видимым без предупреждения и ручного повтора, а восстановление выполняется автоматически
#### Scenario: Server-side fallback и client cached snapshot различаются
- **WHEN** backend возвращает Portfolio с `isStale` либо фоновое обновление Client Cached Snapshot завершается временной ошибкой
- **THEN** frontend сохраняет доступные данные без технических сообщений и не подменяет один внутренний вид состояния другим
#### Scenario: Подтверждённая пустота переживает фоновую ошибку
- **WHEN** текущая выборка ранее успешно вернула полный пустой результат, а её обновление временно не удалось
- **THEN** подтверждённое пустое состояние сохраняется без предупреждения и не заменяется первичной загрузкой
#### Scenario: Изменились параметры выборки
- **WHEN** пользователь применил другой период или открыл другой счёт, а данных новой выборки ещё нет
- **THEN** экран показывает ожидание или недоступность новой выборки и не подписывает прежние суммы новым периодом или счётом
#### Scenario: Обновление не сбрасывает работу пользователя
- **WHEN** пользователь раскрыл раздел, выбрал фильтры или вводит данные, а фоновое чтение завершилось
- **THEN** обновление не сбрасывает эти действия, не переносит фокус и не возвращает пользователя в начало сохранившегося содержимого
#### Scenario: Изменился доступ к личным данным
- **WHEN** пользователь вышел, успешно сменил подключение либо получил подтверждённый отказ доступа к текущим данным
- **THEN** прежние личные данные скрываются, а позднее завершение старой загрузки не восстанавливает их
## ADDED Requirements
### Requirement: Временные сбои чтения восстанавливаются автоматически
Frontend SHALL автоматически повторять временно неуспешное чтение и восстанавливать
получение необходимых текущему экрану данных без ручного обновления. Повторы
одной попытки MUST иметь конечный предел и паузы, исключающие непрерывную
череду запросов. При исчерпании предела доступные данные SHALL сохраняться,
а при их отсутствии SHALL показываться локальная недоступность. Возвращение сети,
возвращение пользователя к открытому экрану и повторное открытие экрана MUST
возобновлять получение отсутствующих или требующих обновления данных без
перезагрузки страницы. Свежий полный результат MUST NOT требовать обращения
к источнику только из-за такого события.
Восстановление MUST учитывать сбой чтения результата и сбой самого получения
данных из источника, даже если чтение состояния завершилось успешно. Наблюдение
уже выполняющейся работы MUST NOT запускать её повторно. Уход с экрана MUST
прекращать новые попытки его восстановления; это не требует отменять уже начатую
работу источника. Автоматическое восстановление MUST NOT повторять запросы,
требующие исправления ввода, входа или восстановления доступа, и MUST NOT
переносить старые параметры в новую пользовательскую выборку.
#### Scenario: Кратковременный сбой чтения
- **WHEN** чтение текущей выборки временно не удалось, а следующая разрешённая автоматическая попытка успешна
- **THEN** экран самостоятельно показывает результат без ручного действия и без сообщения о сбое или восстановлении
#### Scenario: Источник завершил работу ошибкой
- **WHEN** чтение сохранённых данных успешно, но получение необходимых новых данных из источника временно не удалось
- **THEN** приложение применяет ограниченное автоматическое восстановление к неуспешной работе, а не считает успешное чтение доказательством её успеха
#### Scenario: Повторы исчерпаны
- **WHEN** временная недоступность сохраняется после конечного предела автоматических попыток
- **THEN** приложение прекращает серию повторов, сохраняет доступный результат либо локальную недоступность без бесконечной загрузки и ожидает следующего события восстановления
#### Scenario: Сеть или пользователь вернулись
- **WHEN** после временного сбоя восстановилась сеть либо пользователь вернулся к экрану с отсутствующим или требующим обновления результатом
- **THEN** получение данных возобновляется автоматически для текущей выборки и при успехе заменяет состояние недоступности содержимым
#### Scenario: Несколько событий восстановления совпали
- **WHEN** сеть восстановилась одновременно с возвращением пользователя на экран
- **THEN** приложение не запускает дублирующую работу получения одного набора данных
#### Scenario: Работа источника ещё выполняется
- **WHEN** приложение восстанавливает чтение состояния уже выполняющейся работы
- **THEN** оно продолжает получение её результата без повторного запуска той же работы
#### Scenario: Пользователь покинул экран
- **WHEN** пользователь ушёл со страницы с ожидающим восстановлением
- **THEN** новые попытки для покинутого экрана прекращаются, а уже начатая работа не изменяет содержимое другой выборки
#### Scenario: Ошибка требует действия пользователя
- **WHEN** подтверждены недействительное подключение, отсутствие доступа или некорректный ввод
- **THEN** автоматическая серия повторов прекращается и появляется соответствующее действие или локальная ошибка без технической кнопки повторной загрузки
### Requirement: Неполный результат не выдаётся за окончательный
Frontend MUST различать неизвестный результат, доступную часть данных и
подтверждённую пустоту. Неполные суммы и сравнения MUST NOT показываться как
окончательные или заменяться нулями. Доступные записи MAY оставаться видимыми,
но их список MUST NOT представляться полным при неподтверждённой полноте.
Состояние SHALL относиться к содержимому конкретной области, например
недоступному итогу, без отчёта о прогрессе синхронизации. Готовая область MUST
NOT ждать догрузки истории, которая не влияет на её результат.
#### Scenario: Итог ещё не определён
- **WHEN** часть данных, необходимых для суммы или сравнения, отсутствует
- **THEN** соответствующее значение обозначается как недоступное без вымышленного нуля или окончательного частичного итога
#### Scenario: Пустота ещё не подтверждена
- **WHEN** записи не получены и полнота выбранного периода неизвестна
- **THEN** экран не сообщает об отсутствии операций или выплат за этот период
#### Scenario: Период готов раньше остальной истории
- **WHEN** данные показанного периода полны, а не влияющая на него история ещё загружается
- **THEN** пользователь видит готовый результат без сообщения о догрузке остальной истории
@@ -0,0 +1,52 @@
## MODIFIED Requirements
### Requirement: Контекстная индикация действий и фонового обновления
Frontend MUST сохранять уже доступное содержимое или элемент управления
основным контекстом, когда выполняется пользовательское действие либо фоновое
обновление Client Cached Snapshot. Явное действие пользователя SHALL иметь
компактную индикацию ожидания рядом с соответствующим элементом управления.
Фоновое обновление при доступном результате MUST проходить без индикаторов,
технических сообщений и объявлений о ходе работы. В обоих случаях frontend
MUST NOT заменять уже доступный контекст skeleton placeholders.
#### Scenario: Выполняется пользовательское действие
- **WHEN** User запустил действие и его результат ещё ожидается
- **THEN** frontend сохраняет соответствующий элемент управления или окружающий контекст видимым, показывает компактную индикацию процесса и не заменяет интерфейс skeleton placeholders
#### Scenario: Обновляется уже показанный snapshot
- **WHEN** frontend обновляет данные при наличии видимого Client Cached Snapshot
- **THEN** frontend оставляет snapshot видимым без индикации фонового обновления и без замены snapshot skeleton placeholders
## ADDED Requirements
### Requirement: Первичное ожидание не становится бесконечной загрузкой
При отсутствии результата frontend SHALL сначала показывать структурное
ожидание соответствующей области. После неуспешного завершения получения
либо истечения ограниченного времени первичного ожидания область MUST переходить
в спокойное локализованное состояние недоступности без технических подробностей
и ручного повтора. Это состояние MUST NOT утверждать, что результат пустой,
или требовать перезагрузки страницы. Продолжающееся автоматическое восстановление
MUST NOT заставлять область постоянно переключаться между недоступностью и
skeleton. После успешного получения содержимое SHALL появляться автоматически.
Отсутствие результата одной области MUST NOT скрывать остальные доступные
области. Первичная загрузка MUST сохранять доступное обозначение занятости,
а состояние недоступности MUST NOT продолжать объявляться бесконечно занятым.
#### Scenario: Первый результат успешно получен
- **WHEN** экран ожидает первый результат и получает его в пределах первоначального ожидания
- **THEN** структурные placeholders сменяются содержимым либо подтверждённым пустым состоянием
#### Scenario: Первое получение данных не удалось
- **WHEN** у области нет результата и первоначальная загрузка завершилась ошибкой или превысила ограниченное время ожидания
- **THEN** область показывает нейтральную недоступность без кнопки повтора, вымышленных данных и бесконечного индикатора занятости
#### Scenario: Восстановление продолжается после недоступности
- **WHEN** область уже показывает недоступность и выполняется очередная разрешённая попытка восстановления
- **THEN** область сохраняет устойчивое состояние без мигания skeleton и автоматически показывает содержимое после успеха
#### Scenario: Один блок недоступен
- **WHEN** основной результат загружен, а дополнительный блок не получил данных
- **THEN** состояние недоступности ограничено этим блоком и не заменяет доступное содержимое страницы
@@ -0,0 +1,20 @@
## MODIFIED Requirements
### Requirement: Ожидаемые finance states не становятся route failures
Frontend SHALL сохранять локальное отображение известных состояний Brokerage
Account, Portfolio и T-Bank connection. Эти состояния MUST NOT заменяться
глобальным fallback. Временная ошибка фонового чтения MUST сохранять доступный
результат без предупреждения об устаревании, технической ошибки и ручного
повтора. При отсутствии результата SHALL показываться локальная недоступность
с автоматическим восстановлением. Подтверждённая необходимость входа,
восстановления подключения или выбора доступного счёта MUST сохранять понятное
объяснение и соответствующее действие, не маскируясь сохранёнными личными данными.
#### Scenario: Portfolio Snapshot устарел
- **WHEN** frontend получает устаревший Portfolio Snapshot в действующей границе доступа
- **THEN** frontend отображает Portfolio без предупреждения об устаревании и без глобального fallback
#### Scenario: Ошибка T-Bank connection
- **WHEN** запрос T-Bank connection завершается известной ошибкой
- **THEN** frontend сохраняет локальное состояние: временный сбой чтения восстанавливается автоматически, а подтверждённая необходимость действия пользователя сопровождается этим действием вместо глобального fallback
@@ -0,0 +1,112 @@
## Purpose
Определяет спокойную и понятную обратную связь во всех разделах WealthLedger:
пользователь видит финансовые данные и результат своих действий, а не состояние
внутреннего процесса получения данных.
## ADDED Requirements
### Requirement: Чтение данных не требует внимания к техническим состояниям
На экранах счетов, портфеля, операций, доходов, событий, инструментов, поиска,
карт и настроек подключения приложение MUST NOT показывать время получения
данных, признаки их технического устаревания, ход фонового обновления и сообщения
об успехе либо временной ошибке такого обновления. Приложение MUST NOT предоставлять
кнопки или иные действия ручного обновления и повторной загрузки, включая их
иконные варианты. Эти сведения MUST NOT переноситься в уведомления, подсказки
или объявления вспомогательных технологий. Даты операций, выплат, открытия
счёта и выбранных периодов MUST сохранять свой предметный смысл.
Запрет технических действий MUST NOT удалять применение фильтров, переходы между
страницами, поиск, отправку формы, вход, подключение банка или безопасную навигацию.
После неуспешного сохранения пользователь SHALL иметь возможность вновь отправить
сохранённый ввод обычным действием формы.
#### Scenario: Фоновая загрузка завершилась ошибкой
- **WHEN** на любом из перечисленных экранов уже есть доступный результат и его фоновое обновление временно не удалось
- **THEN** результат остаётся видимым без предупреждения, времени получения, индикатора, уведомления и действия ручного повтора
#### Scenario: Фоновая загрузка завершилась успешно
- **WHEN** приложение получает обновлённые данные без явного действия сохранения со стороны пользователя
- **THEN** содержимое обновляется без сообщения об успешной загрузке и без переноса фокуса
#### Scenario: Просмотр списка карт и операций карты
- **WHEN** временная ошибка возникает при обновлении уже показанных карт, сводки или операций выбранной карты
- **THEN** соответствующий результат остаётся доступным без технического предупреждения и ручного повтора, а действия работы с картой сохраняются
#### Scenario: Поиск или новый диапазон инструмента пока недоступен
- **WHEN** пользователь изменил поисковый запрос либо диапазон графика, а соответствующий результат ещё не получен
- **THEN** экран показывает ожидание или локальную недоступность нового результата без кнопки повтора и не выдаёт результат прежнего запроса или диапазона за новый
#### Scenario: Предметные даты остаются видимыми
- **WHEN** экран показывает дату операции, выплаты или выбранный период
- **THEN** эти даты остаются доступны независимо от отсутствия времени технического получения данных
### Requirement: Краткое уведомление подтверждает неочевидный результат действия
Приложение SHALL показывать краткое локализованное уведомление об успешном
явном действии пользователя, если после завершения на экране отсутствует
однозначное подтверждение результата. Если результат уже очевиден по состоянию
формы или содержимого, приложение MUST NOT дублировать его уведомлением.
Уведомление MUST описывать выполненное действие, а не внутренний процесс.
Ожидание действия MUST NOT представляться его успешным завершением.
#### Scenario: Результат действия иначе легко пропустить
- **WHEN** действие успешно завершилось, его форма закрылась, а оставшийся экран не подтверждает результат однозначно
- **THEN** пользователь получает одно краткое уведомление о выполненном действии
#### Scenario: Результат уже очевиден
- **WHEN** успешное сохранение оставляет на экране явное подтверждение результата
- **THEN** приложение не добавляет дублирующее уведомление
#### Scenario: Приём действия ещё не означает успех
- **WHEN** приложение только начало выполнение действия и окончательный результат неизвестен
- **THEN** оно не показывает уведомление об успешном завершении
### Requirement: Уведомления не мешают работе и не дублируются
Уведомления MUST быть доступны как текст, не полагаться только на цвет и не
перехватывать клавиатурный фокус. Подтверждение успеха SHALL автоматически
закрываться и позволять доступное ручное закрытие. Повторная обработка одного
результата MUST NOT создавать повторное уведомление. Приложение MUST ограничивать
число одновременно видимых уведомлений и MUST NOT создавать каскад сообщений
из-за фонового восстановления данных.
#### Scenario: Один результат обработан повторно
- **WHEN** приложение повторно обрабатывает результат того же пользовательского действия
- **THEN** пользователь не получает второе уведомление об этом результате
#### Scenario: Работа с клавиатурой продолжается
- **WHEN** уведомление появляется во время ввода или навигации с клавиатуры
- **THEN** текущий фокус сохраняется, текст уведомления доступен вспомогательным технологиям, а действие закрытия имеет понятное доступное имя
#### Scenario: Несколько действий завершились рядом по времени
- **WHEN** несколько различных действий успешно завершаются за короткое время
- **THEN** уведомления соблюдают конечный предел одновременного показа и не блокируют доступ к содержимому и действиям страницы
### Requirement: Ошибки действий и препятствия доступа остаются понятными
Ошибка ввода MUST показываться у соответствующего поля. При неуспешном
сохранении приложение MUST сохранять ввод и показывать понятную локальную ошибку
в контексте действия, не объявляя его успешным. Если продолжение требует входа,
подключения банка или другого действия пользователя, объяснение и применимое
действие MUST оставаться доступны в соответствующей области. Исчезающее
уведомление MUST NOT быть единственным способом сообщить такое препятствие.
Приложение MUST NOT самостоятельно повторять изменение пользовательских данных
только потому, что включено автоматическое восстановление чтения.
#### Scenario: Операция не сохранилась
- **WHEN** пользователь отправил форму операции, а сохранение завершилось ошибкой
- **THEN** введённые значения остаются в форме вместе с локальной ошибкой, обычное действие сохранения снова доступно и подтверждение успеха отсутствует
#### Scenario: Поле содержит недопустимое значение
- **WHEN** пользователь пытается отправить форму с недопустимым значением
- **THEN** ошибка относится к этому полю и не заменяется общим всплывающим уведомлением
#### Scenario: Требуется восстановить доступ
- **WHEN** приложение подтвердило необходимость входа или восстановления подключения
- **THEN** соответствующая область предлагает необходимое действие и сохраняет объяснение до устранения препятствия или ухода пользователя
#### Scenario: Ответ на изменение данных не получен
- **WHEN** соединение прервалось после отправки пользовательского изменения и результат неизвестен
- **THEN** приложение не объявляет успех и не повторяет изменение автоматически как обычную загрузку данных
@@ -0,0 +1,83 @@
## MODIFIED Requirements
### Requirement: Состояния и ошибки Portfolio на клиенте
Клиент SHALL загружать Portfolio при открытии защищённого маршрута
`/accounts/:accountId` и использовать успешный ответ в течение 30 секунд.
Временные сбои и возвращение к экрану с требующими обновления данными SHALL
обрабатываться автоматическим восстановлением по `frontend-data-resilience`.
Клиент MUST NOT предоставлять ручное обновление или повторную загрузку.
При первичной загрузке без успешного результата того же Brokerage Account
клиент MUST показывать структурный skeleton ожидаемого содержимого, включая
сводку, без вымышленных сумм. Область MUST иметь доступное обозначение занятости;
skeleton MUST NOT быть интерактивным или получать фокус. Неуспешное или
затянувшееся первичное получение SHALL переходить в локальную недоступность
по `frontend-loading-presentation`, а не в бесконечную загрузку.
Истечение срока свежести MUST NOT скрывать доступный Client Cached Snapshot.
При обновлении и временной ошибке клиент MUST сохранять данные того же
Portfolio, включая валютные итоги, без индикации фоновой работы собственного
чтения, технических предупреждений об этом чтении и уведомлений. Исходное
`lastSuccessfulAt` и `isStale` MUST сохранять внутренний смысл: их показ или
скрытие MUST NOT делать сохранённый снимок новым успешным результатом или
подтверждением текущего доступа. Server-side stale и ошибка клиентского чтения
MUST NOT подменять друг друга.
Состояние источника из `meta` — время последнего успешного результата, признак
устаревания и исход последней часовой попытки — не относится к собственному
чтению клиента: клиент MUST представлять его текстом либо доступно именованным
элементом по `frontend-data-resilience`, не превращая в действие пользователя.
Клиент MUST показывать штатное empty-состояние только при подтверждённом полном
результате с пустыми `positions` и `cashBalances`, без вымышленных нулевых карточек.
Неполный итог MUST NOT выдаваться за окончательный; доступные независимые данные
MUST сохраняться на экране. При подтверждённом `404` клиент MUST скрыть личный
Portfolio и предложить возврат к списку счетов. При `TBANK_NOT_CONNECTED` или
`INVALID_TBANK_TOKEN` он MUST скрыть личные данные и предложить восстановление
подключения. Эти состояния MUST NOT маскироваться сохранённым снимком или
вызывать техническую повторную загрузку.
#### Scenario: Показ устаревшего Portfolio
- **WHEN** клиент получает успешный ответ с `isStale: true` в действующей границе доступа
- **THEN** клиент показывает позиции, остатки и доступные валютные итоги и обозначает устаревание источника текстом либо доступно именованным элементом, без ручного обновления
#### Scenario: Неуспешная последняя попытка сохраняет Portfolio
- **WHEN** клиент получает полный Portfolio и `meta` с неуспехом последней часовой попытки
- **THEN** он сохраняет Portfolio и исходное время видимыми и отдельно показывает текстовое либо доступно именованное предупреждение о неуспехе попытки, не предлагая ручной повтор
#### Scenario: Недоступный счёт на клиенте
- **WHEN** клиент получает подтверждённый `404` текущего Portfolio
- **THEN** клиент не показывает личный Portfolio, включая ранее сохранённый, и предлагает вернуться к списку Brokerage Account без действия повтора
#### Scenario: Первичная загрузка Portfolio
- **WHEN** клиент ожидает Portfolio этого Brokerage Account без ранее полученного успешного ответа
- **THEN** он показывает структурный skeleton, включая место сводки, с доступным обозначением загрузки, без вымышленных или нулевых сумм и без фокусируемых placeholders
#### Scenario: Обновление после истечения срока свежести
- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое обновление данных того же Brokerage Account
- **THEN** клиент сохраняет прежние валютные итоги без индикации обновления и не заменяет доступный состав и сводку skeleton-состоянием
#### Scenario: Ошибка обновления после успешного ответа
- **WHEN** клиент уже показывает Client Cached Snapshot Portfolio, а обновление этих же данных завершается временной ошибкой
- **THEN** клиент сохраняет данные и суммы без предупреждения и действия повтора; ошибка не делает снимок свежим и не изменяет `isStale`
#### Scenario: Ошибка клиентского обновления при server-side stale
- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое обновление этого же Portfolio завершается временной ошибкой
- **THEN** прежние суммы остаются видимыми без технических предупреждений и ручных действий, сохраняя различие внутренних состояний
#### Scenario: Повторное обновление успешно
- **WHEN** после временной ошибки автоматическое восстановление получает успешный Portfolio Snapshot
- **THEN** до ответа прежние суммы остаются видимыми, затем клиент показывает новый результат без уведомления об обновлении и без переноса пользовательского фокуса
#### Scenario: Штатный пустой Portfolio на клиенте
- **WHEN** успешно полученный полный Portfolio содержит пустые `positions` и `cashBalances`
- **THEN** клиент показывает существующее empty-состояние без валютных карточек с вымышленными нулевыми итогами
#### Scenario: Первичный Portfolio пока недоступен
- **WHEN** данных Portfolio нет, а первичное получение не удалось или превысило ограниченное время ожидания
- **THEN** клиент показывает нейтральную недоступность без кнопки повтора и автоматически заменяет её содержимым после успешного восстановления
#### Scenario: Требуется восстановить подключение
- **WHEN** клиент получает `TBANK_NOT_CONNECTED` или `INVALID_TBANK_TOKEN` при наличии сохранённого личного Portfolio
- **THEN** личные данные скрываются, а клиент предлагает необходимое действие подключения без технической повторной загрузки
@@ -0,0 +1,45 @@
## MODIFIED Requirements
### Requirement: Управление подключением в настройках
Клиент SHALL показывать в настройках статус T-Bank connection, текст о
необходимости read-only T-Bank Invest token, поле token и действие подключения
либо замены. Для существующего подключения клиент MUST показывать действие
удаления с modal-подтверждением. Во время создания, замены или удаления все
действия секции подключения и действия modal MUST быть отключены. После
успешного создания или замены клиент MUST очистить поле token и обновить статус;
при ошибке token MUST остаться в поле.
Временный сбой чтения статуса SHALL восстанавливаться автоматически без кнопки
повтора и технического предупреждения. Пока первый статус неизвестен, клиент
MUST NOT показывать форму как подтверждённое отсутствие подключения: после
первичного ожидания SHALL отображаться нейтральная недоступность секции. Если
статус уже известен для текущей сессии, временный сбой его фонового чтения MUST
NOT скрывать доступную секцию или очищать ввод. Подтверждённое изменение
сессии или доступа MUST NOT маскироваться старым статусом.
Ошибки создания, замены и удаления MUST оставаться локальными, сохранять ввод
и возвращать применимые действия формы. Эти действия MUST NOT повторяться
автоматически как чтение статуса. Подтверждения успеха SHALL следовать
`frontend-user-feedback`: очевидное изменение статуса не требует дублирующего
уведомления, неочевидный результат получает одно краткое подтверждение.
#### Scenario: Пользователь подтверждает удаление
- **WHEN** User подтверждает удаление в modal
- **THEN** клиент блокирует действия секции до результата и после успешного ответа показывает состояние без подключения
#### Scenario: Ошибка загрузки статуса
- **WHEN** клиент временно не может получить статус подключения
- **THEN** он автоматически восстанавливает чтение, сохраняет известный статус текущей сессии либо показывает нейтральную недоступность после первичного ожидания без формы и кнопки повтора
#### Scenario: Ошибка проверки token в клиенте
- **WHEN** создание или замена получает `INVALID_TBANK_TOKEN` либо `TBANK_UNAVAILABLE`
- **THEN** клиент показывает соответственно локализованное сообщение о token и правах либо о временной недоступности T-Bank в контексте формы, возвращает интерактивность и сохраняет введённый token
#### Scenario: Фоновое чтение не сбрасывает ввод
- **WHEN** пользователь редактирует token в доступной секции, а фоновое чтение статуса временно не удалось
- **THEN** ввод и доступная секция сохраняются без технического предупреждения или уведомления
#### Scenario: Удаление не удалось
- **WHEN** пользователь подтвердил удаление подключения, а действие завершилось ошибкой
- **THEN** клиент не показывает успех, сохраняет известный статус и локальную ошибку, возвращает применимые действия и не повторяет удаление автоматически
@@ -0,0 +1,159 @@
# Implementation tasks
Основание: согласованные Delta Specs и [Design](design.md), включая inline
UI/UX package `quiet-user-feedback-ui-v1` и Solution Assessment.
Задачи выполняются по порядку групп и пунктов. Каждая задача имеет одного
владельца репозитория; технические проверки выполняет implementer. Проверки
сети используют synthetic fixtures и существующий MSW, а не реальный T-Bank.
Перед Node-командами в каждом репозитории выполняется `nvm use --silent`.
Указанные ниже проверки — план, а не свидетельство выполненной реализации.
Перед изменениями сохраняются чужие staged/unstaged/new files, в частности
перенос PortfolioStructure. База требований сверяется по Migration Plan Design:
изменение порядка интеграции `persist-tbank-data` требует согласования
затронутых artifacts, а не молчаливого перезаписывания active change.
Recovery и удаление ручных controls поставляются вместе. Backend и workspace
не получают implementation tasks: они не входят в affected Delivery Map.
## 1. Принятые решения и зависимости
- [x] 1.1 [frontend] Применить Solution Assessment: оставить TanStack Query и существующих resource/screen owners, добавить exact `@mantine/notifications@9.6.1` с согласованным lockfile без второго query runtime или toast framework.
- **Covers:** `N/A` — dependency preparation не вводит отдельного пользовательского поведения; реализует выбранные средства из Design §1 и §6 до custom recovery work.
- **Verification:** `npm ci`; `npm ls @mantine/core @mantine/hooks @mantine/notifications react react-dom`; сверить exact versions, peer compatibility и отсутствие посторонних dependency changes.
## 2. Ограниченное восстановление чтения
- [x] 2.1 [frontend] В существующем `shared/api` реализовать классификацию временных GET failures, 15-секундный deadline с отменой чтения тела и до двух повторов с паузами 1/2 секунды; исключить auth endpoints, mutations, access/domain errors и отмену.
- **Covers:** `frontend-data-resilience` — `Временные сбои чтения восстанавливаются автоматически`; `frontend-user-feedback` — `Ошибки действий и препятствия доступа остаются понятными`.
- **Verification:** MSW-проверки через public client/query interface: recovery после network/timeout/503, конечное число запросов, прерванное чтение тела, отсутствие retries для 401/403/404/409 и malformed result, поведение 429 и серверной отсрочки по Design; `npm run test:run -- src/shared/api`.
- [x] 2.2 [frontend] Подключить read policy к authenticated query hooks и `useResourceQuery`, сохранив одну generated cache identity, независимые ordinary/observe режимы и существующие session/access fences; убрать локальные запреты retry только там, где они заменяются принятой политикой.
- **Covers:** `frontend-data-resilience` — `Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio`; `frontend-data-resilience` — `Временные сбои чтения восстанавливаются автоматически`.
- **Verification:** Расширить resource lifecycle MSW-сценарии: cached snapshot при ошибке, `data: null` при HTTP success, ordinary GET после observe, unmount/cancel и late response после logout/connection replacement; `npm run test:run -- src/shared/api src/features/tbank-connection/model/connection-boundary`.
- [x] 2.3 [frontend] Перевести `useScreenSync` на ограниченное восстановление failed/cancelled missing/stale источников обычным GET: две дополнительные source attempts с паузами 5/15 секунд, бюджет на текущую выборку и отсутствие автоматического force POST.
- **Covers:** `frontend-data-resilience` — `Временные сбои чтения восстанавливаются автоматически`; `frontend-data-resilience` — `Неполный результат не выдаётся за окончательный`.
- **Verification:** MSW + управляемое время: failed source при HTTP 200, новая work ID без сброса бюджета, исчерпание серии, fresh complete после неудачного force, permanent unavailableReason; проверить число ordinary GET и отсутствие POST `/api/v1/tbank-sync`; `npm run test:run -- src/features/tbank-connection/model`.
- [x] 2.4 [frontend] Согласовать status observation и заключительное observe-чтение с in-flight запросами, сохранив интервал 1 секунда только для running work; различить status ID not found и подтверждённый отказ доступа к ресурсу.
- **Covers:** `frontend-data-resilience` — `Временные сбои чтения восстанавливаются автоматически`; `frontend-data-resilience` — `Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio`.
- **Verification:** MSW-сценарии: медленное observe не отменяется каждым tick, terminal status не теряет заключительное чтение, HTTP/status failure не запускает force sync, `404` work ID перечитывает ресурс, access loss очищает личный snapshot; `npm run test:run -- src/features/tbank-connection/model/screen-sync.test.tsx src/shared/api/resource-query.test.tsx`.
- [x] 2.5 [frontend] Подключить единый lifecycle recovery на focus/reconnect и смену выборки: обычные reads используют штатную Query policy, инвестиционные — screen owner; отменять неактуальные timers и не дублировать восстановление при совпадении событий.
- **Covers:** `frontend-data-resilience` — `Временные сбои чтения восстанавливаются автоматически`; `frontend-data-resilience` — `Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio`.
- **Verification:** Через Query focus/online managers и MSW проверить fresh/stale/missing results, offline/hidden pause, одновременный reconnect/focus, повторное открытие, переход на другой счёт/период и уход последнего consumer; `npm run test:run -- src/shared/api src/features/tbank-connection/model`.
## 3. Состояния ожидания и доступного результата
- [x] 3.1 [frontend] Реализовать общую presentation-only модель ограниченного первичного ожидания в `shared/lib`: 10-секундный budget, устойчивый unavailable latch для identity области и автоматический переход к результату; применить её к существующим loading/state presentation без второго кэша.
- **Covers:** `frontend-loading-presentation` — `Первичное ожидание не становится бесконечной загрузкой`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления`.
- **Verification:** Минимальная runnable проверка собственного state transition: skeleton → timeout/error unavailable → loaded, отсутствие мигания при retry, сброс только при новой выборке, cleanup и доступное busy-состояние; `npm run test:run -- src/shared/lib` и `npm run typecheck`.
- [x] 3.2 [frontend] В общих investment section models разделить readiness, полноту и access failure: успешные пустые данные не теряются, missing/partial не становятся нулевым итогом, а технические metadata остаются у владельца данных.
- **Covers:** `frontend-data-resilience` — `Неполный результат не выдаётся за окончательный`; `frontend-ui-resilience` — `Ожидаемые finance states не становятся route failures`.
- **Verification:** Проверки через потребляющие views: `null` против полного `[]`, partial rows против полного empty, недоступный total/comparison, independent ready section и access failure при cached content; `npm run test:run -- src/features/instrument-details src/pages/account-income`.
## 4. Инвестиционные экраны без технических controls
- [x] 4.1 [frontend] Перевести Accounts и AccountPage, включая «Все счета», на новые состояния: убрать даты получения, refresh/retry и технические предупреждения, сохранить валютные итоги, доступные snapshots, контекст и предметную навигацию.
- **Covers:** `brokerage-accounts` — `Экран списка брокерских счетов`; `brokerage-accounts` — `Клиентские состояния ошибки списка счетов`; `portfolio-view` — `Состояния и ошибки Portfolio на клиенте`; `frontend-data-resilience` — `Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio`; `frontend-ui-resilience` — `Ожидаемые finance states не становятся route failures`.
- **Verification:** RTL/MSW: initial/loaded/empty/partial/unavailable, quiet refetch failure, сохранение раскрытий и focus, отсутствие вымышленных нулей, `404` и invalid token скрывают снимок; `npm run test:run -- src/pages/accounts src/pages/account src/app/routes/AppRoutes/AllAccounts.test.tsx`.
- [x] 4.2 [frontend] Перевести полный журнал и recent operations на локальное тихое восстановление; сохранить применённые фильтры, cursor navigation, даты и смысл строк, не подменять новую страницу прежней.
- **Covers:** `brokerage-account-ledger` — `Account overview includes recent ledger events`; `brokerage-account-ledger` — `Журнал восстанавливает чтение без ручного обновления`.
- **Verification:** RTL/MSW: background failure с прежними записями, missing новой страницы, отсутствие false empty и retry, локальный сбой preview не скрывает Portfolio; `npm run test:run -- src/pages/account-operations src/pages/account`.
- [x] 4.3 [frontend] Перевести страницу будущих событий и preview на сохранение доступного результата и нейтральную локальную недоступность; убрать технические статусы, сохранив даты и ограничения финансового прогноза.
- **Covers:** `brokerage-account-upcoming-events` — `Account page presents an upcoming events list`; `brokerage-account-upcoming-events` — `Account overview includes upcoming events`.
- **Verification:** RTL/MSW: cached events при временной ошибке, неизвестный результат против полного empty, offer без суммы, потеря доступа и независимость preview; `npm run test:run -- src/pages/account-events src/entities/brokerage-event src/pages/account`.
- [x] 4.4 [frontend] Перевести доход и current-month summary на тихое обновление; оставить независимые готовые результаты, недоступные comparisons без нулевой базы и неизменный пользовательский черновик периода.
- **Covers:** `brokerage-account-income-analytics` — `Realized analytics never presents an incomplete result as complete`; `brokerage-account-income-analytics` — `Account overview has an independent current-month income summary`; `brokerage-account-income-analytics` — `Доход обновляется без технических сообщений`; `frontend-data-resilience` — `Неполный результат не выдаётся за окончательный`.
- **Verification:** RTL/MSW: полный старый результат при временном сбое, missing/partial текущего периода, готовый доход с недоступным сравнением, смена периода без переименования прежних сумм, сохранение черновика; `npm run test:run -- src/pages/account-income src/pages/account`.
- [x] 4.5 [frontend] Убрать refresh/retry из поиска акций и облигаций, связав VM с автоматическим recovery и ожиданием текущего debounced query; сохранить фильтры и отсутствие подмены результата прежним запросом.
- **Covers:** `frontend-user-feedback` — `Чтение данных не требует внимания к техническим состояниям`; `frontend-loading-presentation` — `Первичное ожидание не становится бесконечной загрузкой`.
- **Verification:** RTL/MSW: смена query во время запроса, delayed/failed поиск с последующим восстановлением, отсутствие false no-results и технических controls; `npm run test:run -- src/features/instrument-search src/pages/stocks src/pages/bonds`.
- [x] 4.6 [frontend] Перевести детали акции/облигации и их секции на readiness текущей выборки: график, личные позиции, история, сведения и выплаты сохраняют доступное содержимое без технических дат и действий.
- **Covers:** `frontend-user-feedback` — `Чтение данных не требует внимания к техническим состояниям`; `frontend-data-resilience` — `Неполный результат не выдаётся за окончательный`; `frontend-ui-resilience` — `Ожидаемые finance states не становятся route failures`.
- **Verification:** RTL/MSW: independent sections, новый диапазон свечей и instrument ID не показывают прежний результат как новый, partial history не является полной; `npm run test:run -- src/features/instrument-details src/pages/stock src/pages/bond`.
## 5. Карты, формы и настройки подключения
- [x] 5.1 [frontend] Перевести CardsPage, CardPage и BankSummary на тихое чтение, ограниченное ожидание и сохранение текущей выборки; удалить placeholder прежней страницы при переходе к другой странице банковских операций.
- **Covers:** `frontend-user-feedback` — `Чтение данных не требует внимания к техническим состояниям`; `frontend-data-resilience` — `Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio`; `frontend-loading-presentation` — `Первичное ожидание не становится бесконечной загрузкой`.
- **Verification:** RTL/MSW: карты/summary/history при refetch failure, `page` и период меняются без подмены данных, полный empty отличается от missing, формы не размонтируются из-за фоновой ошибки; `npm run test:run -- src/pages/cards src/pages/card/ui/CardPage src/entities/bank-card/api`.
- [x] 5.2 [frontend] Сохранить ввод и предметные действия BankCardForm и CategoryManager, заменив техническое перечитывание автоматической загрузкой; актуальную версию после конфликта принимать только явно, не повторяя запись.
- **Covers:** `frontend-user-feedback` — `Ошибки действий и препятствия доступа остаются понятными`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления`.
- **Verification:** RTL/MSW: field errors, сохранённый ввод при network failure, version conflict → автоматическое чтение → явное принятие версии, доступная отмена при unavailable, отсутствие второго mutation request; `npm run test:run -- src/entities/bank-card/ui`.
- [x] 5.3 [frontend] Перевести чтение категорий, исходной покупки и версий OperationForm/DeleteOperation на автоматическое восстановление; сохранить intent ID, refund constraints, явное подтверждение версии и недопущение повторной финансовой записи.
- **Covers:** `frontend-user-feedback` — `Ошибки действий и препятствия доступа остаются понятными`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления`.
- **Verification:** RTL/MSW: сбой зависимого GET без потери ввода, отсутствие ложного empty для расходов, конфликт удаления/правки, network loss после submit без auto-resubmit, сохранность version/intent при ручной повторной отправке; `npm run test:run -- src/pages/card/ui/OperationForm src/pages/card/ui/DeleteOperation src/entities/bank-card/api/banking.test.tsx`.
- [x] 5.4 [frontend] Перевести чтение T-Bank connection status на тихое восстановление: не показывать форму до первого известного статуса, сохранять уже открытую секцию и token при временном GET failure, оставить явные локальные ошибки mutations.
- **Covers:** `tbank-connection` — `Управление подключением в настройках`; `frontend-user-feedback` — `Ошибки действий и препятствия доступа остаются понятными`; `frontend-ui-resilience` — `Ожидаемые finance states не становятся route failures`.
- **Verification:** RTL/MSW: initial unavailable → loaded, сохранность ввода при фоновом сбое, single mutation на submit/delete, ошибки token и удаления, новая cache boundary только после успешной mutation; `npm run test:run -- src/features/tbank-connection`.
## 6. Редкие подтверждения действий
- [x] 6.1 [frontend] Подключить один Mantine Notifications host с общей темой, styles и i18n: limit 2, autoClose 4000 ms, desktop/mobile positions, доступное закрытие, reduced motion и очистка очереди при смене/завершении сессии.
- **Covers:** `frontend-user-feedback` — `Уведомления не мешают работе и не дублируются`.
- **Verification:** Проверить application-owned cleanup при logout/смене сессии через RTL; выполнить `npm run typecheck`; конфигурацию third-party limit/timer не дублировать unit-тестами библиотеки, доступность и размещение проверить в задаче 7.3.
- [x] 6.2 [frontend] В action VMs карт, операций и категорий отделить успех записи от последующего чтения и реализовать однократное подтверждение по таблице Design: видимый результат без toast, неочевидный — краткое сообщение с action ID и ограниченным ожиданием подтверждения.
- **Covers:** `frontend-user-feedback` — `Краткое уведомление подтверждает неочевидный результат действия`; `frontend-user-feedback` — `Уведомления не мешают работе и не дублируются`; `frontend-user-feedback` — `Ошибки действий и препятствия доступа остаются понятными`.
- **Verification:** RTL/MSW: сохранённая запись внутри/вне видимой выборки, удаление видимой/невидимой записи, успешная запись с неуспешным GET, 10-секундный предел подтверждения, повторный callback того же action ID и отдельное следующее действие; `npm run test:run -- src/entities/bank-card/ui src/pages/card src/pages/cards`.
- [x] 6.3 [frontend] В VM подключения добавить одно подтверждение успешной замены token при неизменном видимом статусе; создание/удаление с очевидным изменением статуса и все фоновые чтения оставить без toast.
- **Covers:** `tbank-connection` — `Управление подключением в настройках`; `frontend-user-feedback` — `Краткое уведомление подтверждает неочевидный результат действия`; `frontend-user-feedback` — `Чтение данных не требует внимания к техническим состояниям`.
- **Verification:** RTL/MSW: create/delete без дублирующего подтверждения, replace с единственным toast, failure без success, background recovery без notifications, сообщение без token; `npm run test:run -- src/features/tbank-connection/ui`.
## 7. Завершение frontend и UI-проверки
- [x] 7.1 [frontend] После миграции consumers удалить InvestmentRefreshState, SourceSyncStatus, неиспользуемые props/exports/строки и их устаревшие stories; проверить оставшиеся технические actions во всём scope без удаления предметных form/access controls.
- **Covers:** `frontend-user-feedback` — `Чтение данных не требует внимания к техническим состояниям`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления`.
- **Verification:** Проверить callers и i18n keys, отсутствие видимых/иконных/tooltip/live-region замен удалённых статусов; `npm run knip`; `npm run deps`; `npm run test:run -- src/test/storybook.test.tsx`; убедиться, что внутренние metadata и серверный контракт не удалены.
- [x] 7.2 [frontend] Обновить UI/data/testing conventions и значимые presentation stories под UI package v1: тихий loaded, нейтральный unavailable, partial без ложного итога и редкое подтверждение; сохранить изоляцию Storybook от application API/auth/query.
- **Covers:** `N/A` — conventions и каталог закрепляют уже утверждённую архитектуру и UI-пакет, не вводя отдельного поведения; необходимы для согласованной frontend delivery из Design.
- **Verification:** Сверить локальные инструкции с новыми retry/focus policies, примеры и ссылки; `npm run deps:storybook`; `npm run build:catalog`; проверить достижимость stories и отсутствие устаревшего обещания ручного retry.
- [x] 7.3 [frontend] Выполнить implementation-owned browser review состояний и interactions UI package v1 на 320, 768 и 1440 px: страницы чтения, partial/unavailable, формы, notification host и восстановление контекста.
- **Covers:** `frontend-user-feedback` — `Уведомления не мешают работе и не дублируются`; `frontend-user-feedback` — `Чтение данных не требует внимания к техническим состояниям`; `frontend-loading-presentation` — `Первичное ожидание не становится бесконечной загрузкой`; `frontend-data-resilience` — `Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio`.
- **Verification:** В браузере проверить Tab/focus, modal trap и доступное закрытие, busy/live announcements, reduced motion, длинный текст, safe area, отсутствие перекрытых controls/горизонтального скролла, сохранение scroll и раскрытий; presentation states воспроизвести synthetic stories, runtime lifecycle — приложением и RTL/MSW, без browser MSW worker.
- [x] 7.4 [frontend] Завершить Boy Scout review затронутого diff и выполнить полный canonical frontend gate после всех production, test, dependency и convention изменений.
- **Covers:** `N/A` — финальная проверка frontend delivery по Design, без нового observable behavior; targeted tests не заменяют canonical gate.
- **Verification:** `npm ci` и `npm run verify`; отдельно сверить сохранность чужого PortfolioStructure diff, отсутствие generated/OpenAPI drift, дублированных query owners и global toast interception; записать фактические результаты и ограничения.
## 8. As-built документация
- [x] 8.1 [docs] После frontend delivery оформить принятое решение о тихом чтении и редких уведомлениях в существующем журнале ADR, с причинами, альтернативами, последствиями и связью с предыдущим решением о background sync.
- **Covers:** `N/A` — ADR фиксирует rationale уже утверждённого Design и выполненной delivery, а не создаёт новую behavioral spec; docs отмечен affected в Delivery Map.
- **Verification:** Проверить номер по актуальному `adr/README.md`, самостоятельность текста, связь с ADR 017 и сохранность его исторического содержания; новый ADR описывает реализованный механизм и не обещает непоставленное поведение.
- [x] 8.2 [docs] Обновить объяснения UI resilience, data freshness и visual states, убрав описание обязательных предупреждений/ручного обновления и объяснив ограниченное автоматическое восстановление, первичную недоступность и сохранение ошибок действий.
- **Covers:** `N/A` — as-built тематические страницы объясняют согласованные требования и технические решения; это documentation outcome Design, без самостоятельного нового поведения.
- **Verification:** Сверить `architecture/cross-cutting/ui-resilience.md`, `architecture/cross-cutting/data-freshness.md` и `design/overview.md` с реализованным flow, исключить противоречия между freshness, completeness и UI; проверить локальные ссылки и предметные даты.
- [x] 8.3 [docs] Согласовать затронутые предметные описания счетов, портфеля, операций, дохода, событий, инструментов и карт с тихим UI; обновить только фрагменты, описывающие изменённые состояния и recovery.
- **Covers:** `N/A` — предметная документация устраняет устаревшие инструкции пользователю после frontend delivery; область и поведение уже заданы Design и Delta Specs.
- **Verification:** Пройти маршруты README к затронутым domains; сверить упоминания refresh/retry, времени получения и ошибок с приложением, сохранив полезные финансовые пояснения, ограничения прогнозов и входящие ссылки.
- [x] 8.4 [docs] Выполнить semantic/link review обновлённой документации и canonical docs gate с учётом новых файлов.
- **Covers:** `N/A` — проверка документационной delivery из Design; не является новой пользовательской capability.
- **Verification:** `npm ci` при отсутствии установленных dependencies, затем `npm run verify`; проверить ADR index, новые локальные anchors и пользовательские маршруты чтения; если затронуты диаграммы, отдельно проверить их рендер доступным средством.
## 9. Canonical specifications и итоговые gates
- [x] 9.1 [specs] После подтверждения реализации перенести согласованные quiet Delta Requirements в canonical specs через OpenSpec sync workflow и добавить `frontend-user-feedback` в AI_INDEX; учесть порядок пересечения с persist по Design, сохраняя несвязанные требования.
- **Covers:** `N/A` — canonical publication не вводит поведения сверх утверждённых Delta Specs; реализует обязательный specs outcome Delivery Map и сохраняет единственный источник требований.
- **Verification:** Сравнить перенесённые Requirement blocks со всеми десятью delta paths, проверить отсутствие восстановления старых UI/retry obligations и потери несвязанных scenarios; проверить новый target в AI_INDEX; при изменившейся базе вернуть затронутые artifacts на согласование до переноса.
- [x] 9.2 [specs] Выполнить canonical specs gate, native schema/change/spec validation и итоговую сверку трассировки с реально выполненными задачами.
- **Covers:** `N/A` — проверка полноты и согласованности specs delivery, обязательная по Design и местным правилам; нового observable behavior нет.
- **Verification:** `npm run verify`; `openspec schema validate wealth-ledger-sdd --verbose`; `openspec validate quiet-user-feedback --type change --strict --store wealth-ledger-specs`; `openspec validate --specs --strict --store wealth-ledger-specs`; проверить новые файлы и их diff отдельно, а checklist отмечать только по наблюдённым результатам.