diff --git a/AI_INDEX.md b/AI_INDEX.md index cad74ff..bb04dbf 100644 --- a/AI_INDEX.md +++ b/AI_INDEX.md @@ -25,6 +25,7 @@ types. CI каждого участника использует только е | Каталог компонентов | [UI catalog](openspec/specs/frontend-ui-catalog/spec.md) | | Loading/skeleton | [Loading presentation](openspec/specs/frontend-loading-presentation/spec.md) | | UI error/recovery | [UI resilience](openspec/specs/frontend-ui-resilience/spec.md) | +| Тихое чтение и редкие уведомления | [User feedback](openspec/specs/frontend-user-feedback/spec.md) | | Query coherence/cache | [Query coherence](openspec/specs/frontend-query-coherence/spec.md), [data resilience](openspec/specs/frontend-data-resilience/spec.md) | | OpenAPI generation | [API contract generation](openspec/specs/api-contract-generation/spec.md) | | Logging/request context | [Structured logging](openspec/specs/backend-structured-logging/spec.md) | diff --git a/openspec/changes/quiet-user-feedback/.openspec.yaml b/openspec/changes/quiet-user-feedback/.openspec.yaml new file mode 100644 index 0000000..e6c37f6 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/.openspec.yaml @@ -0,0 +1,2 @@ +schema: wealth-ledger-sdd +created: 2026-10-04 diff --git a/openspec/changes/quiet-user-feedback/design.md b/openspec/changes/quiet-user-feedback/design.md new file mode 100644 index 0000000..1695323 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/design.md @@ -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, чтение ограничено прежней политикой, сохранение и удаление требуют явного действия | diff --git a/openspec/changes/quiet-user-feedback/proposal.md b/openspec/changes/quiet-user-feedback/proposal.md new file mode 100644 index 0000000..736286e --- /dev/null +++ b/openspec/changes/quiet-user-feedback/proposal.md @@ -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 + +Изменение охватывает просмотр счетов, портфеля, операций, доходов, будущих +событий, инструментов и карт, поиск и настройки подключения. + +Пользователю больше не нужно следить за обновлением данных и устранять +временные сбои загрузки вручную. Интерфейс становится спокойнее, а сообщения +помогают понять результат собственного действия или необходимый следующий шаг. +Финансовый смысл данных, понятность ошибок сохранения и защита личной +информации сохраняются. diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md b/openspec/changes/quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md new file mode 100644 index 0000000..8492be4 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md @@ -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** страница показывает ожидание или недоступность нового результата, не выдаёт прежние суммы за доход нового периода и восстанавливается автоматически diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-account-ledger/spec.md b/openspec/changes/quiet-user-feedback/specs/brokerage-account-ledger/spec.md new file mode 100644 index 0000000..06d900c --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/brokerage-account-ledger/spec.md @@ -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** область новой страницы показывает ожидание или нейтральную недоступность, не выдаёт прежние записи за новую страницу и восстанавливает чтение автоматически diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md b/openspec/changes/quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md new file mode 100644 index 0000000..7abe5db --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md @@ -0,0 +1,49 @@ +## MODIFIED Requirements + +### Requirement: Account page presents an upcoming events list + +Страница Brokerage Account MUST предоставлять действие перехода на отдельную +страницу «Будущие события». Эта страница MUST показывать все полученные события +компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, +инструментом и прогнозной суммой в исходной валюте, если она известна. Страница +MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может +быть неполным из-за доступности данных источника. Это предметное ограничение +прогноза MUST NOT превращаться в отчёт о технической синхронизации. + +При временном сбое обновления страница MUST сохранять ранее доступный результат +той же выборки без технических сообщений, времени получения и ручного повтора. +При отсутствии результата SHALL показываться первичное ожидание либо нейтральная +недоступность с автоматическим восстановлением по `frontend-data-resilience`. +Подтверждённая потеря доступа MUST скрывать личные данные независимо от наличия +сохранённого результата. + +#### Scenario: Просмотр ближайших событий +- **WHEN** User открывает «Будущие события» из обзора Brokerage Account +- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы + +#### Scenario: Пустое состояние страницы +- **WHEN** запрос будущих событий успешно возвращает полный пустой список +- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника + +#### Scenario: Ошибка страницы +- **WHEN** запрос будущих событий завершается временной ошибкой +- **THEN** страница сохраняет доступный результат той же выборки либо показывает нейтральную недоступность без ручного повтора, восстанавливая чтение автоматически и не выдавая частичные данные за полный прогноз + +### Requirement: Account overview includes upcoming events + +Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash +Event и действие перехода на полную страницу «Будущие события». Неуспешный +или пустой запрос этого блока MUST оставаться локальным состоянием блока и +не заменять успешно отображённый Portfolio. При временном сбое обновления +блок MUST сохранять доступные события без технического предупреждения; при +отсутствии результата SHALL показываться нейтральная недоступность после +первичного ожидания. Восстановление MUST выполняться автоматически без ручного +повтора и без подмены неизвестного результата подтверждённой пустотой. + +#### Scenario: Отображение ближайших событий +- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события +- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице + +#### Scenario: Ближайшие события недоступны +- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается временной ошибкой +- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние события либо показывает нейтральную недоступность без технической ошибки и ручного повтора diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-accounts/spec.md b/openspec/changes/quiet-user-feedback/specs/brokerage-accounts/spec.md new file mode 100644 index 0000000..b3cc0f3 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/brokerage-accounts/spec.md @@ -0,0 +1,101 @@ +## MODIFIED Requirements + +### Requirement: Экран списка брокерских счетов + +Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` +без ручного обновления. Клиент MUST использовать успешный frontend-кэш списка +в течение 60 секунд. После истечения этого срока открытие или возвращение +на экран SHALL инициировать получение актуального списка; имеющийся Client +Cached Snapshot MUST оставаться видимым во время обновления и после временной +ошибки без технических статусов, времени получения и действий повтора. +Автоматическое восстановление SHALL соответствовать `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: Ошибка фонового обновления не скрывает 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 видимым без предупреждения, времени получения и ручных действий восстановления diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-data-resilience/spec.md b/openspec/changes/quiet-user-feedback/specs/frontend-data-resilience/spec.md new file mode 100644 index 0000000..2e1c160 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/frontend-data-resilience/spec.md @@ -0,0 +1,127 @@ +## 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`. + +Обновление MUST сохранять выбранные фильтры, период, открытые разделы, ввод, +фокус и положение просмотра, пока соответствующее содержимое существует. +При logout, смене подключения или подтверждённом отказе доступа личные данные +прежней границы MUST перестать отображаться; поздний результат прежней загрузки +MUST NOT возвращать их на экран. + +#### Scenario: Список Brokerage Accounts обновляется в фоне +- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается фоновое обновление того же списка +- **THEN** frontend оставляет список видимым без индикации обновления, времени получения и технических действий + +#### 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** пользователь видит готовый результат без сообщения о догрузке остальной истории diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-loading-presentation/spec.md b/openspec/changes/quiet-user-feedback/specs/frontend-loading-presentation/spec.md new file mode 100644 index 0000000..b20165d --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/frontend-loading-presentation/spec.md @@ -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** состояние недоступности ограничено этим блоком и не заменяет доступное содержимое страницы diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-ui-resilience/spec.md b/openspec/changes/quiet-user-feedback/specs/frontend-ui-resilience/spec.md new file mode 100644 index 0000000..d0b9bd5 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/frontend-ui-resilience/spec.md @@ -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 diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-user-feedback/spec.md b/openspec/changes/quiet-user-feedback/specs/frontend-user-feedback/spec.md new file mode 100644 index 0000000..b1b494b --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/frontend-user-feedback/spec.md @@ -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** приложение не объявляет успех и не повторяет изменение автоматически как обычную загрузку данных diff --git a/openspec/changes/quiet-user-feedback/specs/portfolio-view/spec.md b/openspec/changes/quiet-user-feedback/specs/portfolio-view/spec.md new file mode 100644 index 0000000..9589885 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/portfolio-view/spec.md @@ -0,0 +1,73 @@ +## 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 подменять друг друга. + +Клиент 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: Недоступный счёт на клиенте +- **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** личные данные скрываются, а клиент предлагает необходимое действие подключения без технической повторной загрузки diff --git a/openspec/changes/quiet-user-feedback/specs/tbank-connection/spec.md b/openspec/changes/quiet-user-feedback/specs/tbank-connection/spec.md new file mode 100644 index 0000000..ac2845d --- /dev/null +++ b/openspec/changes/quiet-user-feedback/specs/tbank-connection/spec.md @@ -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** клиент не показывает успех, сохраняет известный статус и локальную ошибку, возвращает применимые действия и не повторяет удаление автоматически diff --git a/openspec/changes/quiet-user-feedback/tasks.md b/openspec/changes/quiet-user-feedback/tasks.md new file mode 100644 index 0000000..38d2863 --- /dev/null +++ b/openspec/changes/quiet-user-feedback/tasks.md @@ -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 отмечать только по наблюдённым результатам. diff --git a/openspec/specs/brokerage-account-income-analytics/spec.md b/openspec/specs/brokerage-account-income-analytics/spec.md index 075c588..89c6304 100644 --- a/openspec/specs/brokerage-account-income-analytics/spec.md +++ b/openspec/specs/brokerage-account-income-analytics/spec.md @@ -118,7 +118,23 @@ ### Requirement: Realized analytics never presents an incomplete result as complete -Система MUST формировать выбранный результат, его comparisons, trend buckets, instrument breakdown и drill-down только после успешного получения всех необходимых cursor pages исполненных операций. Ошибка любой необходимой страницы MUST завершать расчёт восстанавливаемой ошибкой и MUST NOT возвращать частичные totals, comparisons, buckets или breakdown как полный результат. Успешный период без подходящих выплат MUST возвращать нулевой результат и отображаться с явным объяснением пустого состояния. +Система 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: Все страницы операций успешно получены @@ -127,17 +143,31 @@ #### Scenario: Одна из страниц операций недоступна -- **WHEN** получение любой необходимой cursor page завершается ошибкой -- **THEN** страница показывает восстанавливаемую ошибку и не показывает частичный результат как полный +- **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 наследовать последний пользовательский период страницы аналитики. Пустой или неуспешный расчёт summary MUST оставаться локальным состоянием блока и MUST NOT заменять успешно отображённый Portfolio или другие блоки обзора. +Обзор выбранного Brokerage Account MUST показывать компактный source-currency +summary «Получено купонами и дивидендами» за период от первого дня текущего +месяца по Europe/Moscow до сегодняшнего дня включительно и действие перехода +на страницу «Пассивный доход». Summary MUST всегда использовать этот current-month +период и MUST NOT наследовать последний пользовательский период страницы аналитики. +Пустой или неуспешный расчёт MUST оставаться локальным состоянием блока и +MUST NOT заменять успешно отображённый Portfolio или другие блоки обзора. +Временная ошибка обновления MUST сохранять доступную сводку той же выборки без +предупреждения и ручного повтора. При отсутствии результата SHALL показываться +нейтральная недоступность после первичного ожидания с автоматическим восстановлением. #### Scenario: Обзор показывает доход текущего месяца @@ -151,8 +181,28 @@ #### Scenario: Current-month summary недоступен -- **WHEN** Portfolio обзора успешно загружен, но расчёт current-month summary завершается ошибкой -- **THEN** Portfolio и остальные успешно загруженные блоки остаются доступными, а summary показывает собственное состояние восстановления +- **WHEN** Portfolio обзора успешно загружен, но расчёт current-month summary завершается временной ошибкой +- **THEN** Portfolio и остальные блоки остаются доступными, а summary сохраняет предыдущий результат той же выборки либо показывает нейтральную недоступность без ручного повтора + +### Requirement: Доход обновляется без технических сообщений + +Страница дохода MUST сохранять применённый период, черновик фильтров и +доступный результат при фоновом обновлении. Временная ошибка SHALL +восстанавливаться автоматически без кнопок обновления и повтора, времени +получения данных и уведомлений об их загрузке. Изменение периода MUST NOT +подписывать прежний результат новыми датами. Ошибки пользовательского периода +MUST оставаться у полей и MUST NOT запускать автоматический повтор ошибочного +ввода. Подтверждённая потеря доступа MUST скрывать личные результаты. + +#### Scenario: Доход обновляется при редактировании периода + +- **WHEN** пользователь редактирует черновик дат, а фоновое обновление применённого периода завершилось +- **THEN** черновик и фокус сохраняются, результат относится к прежнему применённому периоду и техническое уведомление не появляется + +#### Scenario: Новый период пока недоступен + +- **WHEN** пользователь применил другой допустимый период, но его расчёт ещё не получен +- **THEN** страница показывает ожидание или недоступность нового результата, не выдаёт прежние суммы за доход нового периода и восстанавливается автоматически ### Requirement: Income payments remain traceable to exact Ledger Entries diff --git a/openspec/specs/brokerage-account-ledger/spec.md b/openspec/specs/brokerage-account-ledger/spec.md index 1cf98d8..85f3587 100644 --- a/openspec/specs/brokerage-account-ledger/spec.md +++ b/openspec/specs/brokerage-account-ledger/spec.md @@ -48,20 +48,48 @@ - **THEN** запись журнала передаёт её вместе с доступными идентификаторами инструмента, не меняя остальные сведения операции ### Requirement: Account overview includes recent ledger events -Обзор счёта MUST показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Каждая показанная запись MUST иметь ту же визуальную идентичность инструмента и семантику денежного движения, что и запись полного журнала. Неуспешный или пустой запрос последних записей MUST оставаться локальным состоянием блока последних событий и MUST NOT заменять успешно отображённый обзор Portfolio. + +Обзор счёта MUST показывать пять последних исполненных записей журнала этого +счёта и действие перехода к его полному журналу. Каждая запись MUST иметь ту же +визуальную идентичность инструмента и семантику денежного движения, что и запись +полного журнала. Неуспешный или пустой запрос MUST оставаться локальным состоянием +блока и MUST NOT заменять успешно отображённый Portfolio. При временном сбое +обновления блок MUST сохранять доступные записи без технического предупреждения +или ручного повтора. При отсутствии результата он SHALL показывать первичное +ожидание либо нейтральную недоступность, автоматически восстанавливая чтение. +Неподтверждённая пустота MUST NOT представляться отсутствием операций. #### Scenario: Отображение последних событий - **WHEN** загружается обзор счёта и его журнал содержит исполненные операции - **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал #### Scenario: Последние записи недоступны -- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой -- **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления +- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается временной ошибкой +- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние записи либо показывает нейтральную недоступность без технического повтора и восстанавливается автоматически #### Scenario: Последняя запись не содержит visual metadata инструмента - **WHEN** одна из последних записей не имеет доступной presentation metadata инструмента - **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения +### Requirement: Журнал восстанавливает чтение без ручного обновления + +Страница журнала SHALL сохранять записи текущей выборки при временном сбое +обновления и восстанавливать чтение автоматически по `frontend-data-resilience`. +Период, категории и выбранная страница MUST сохраняться; результат другой +выборки MUST NOT подменять текущую. Страница MUST NOT показывать время получения, +технические статусы или действия ручного повтора. Даты операций, фильтры, +действия их применения и переходы по страницам MUST оставаться доступными. +При отсутствии результата SHALL применяться первичное ожидание и нейтральная +недоступность, а не сообщение об отсутствии операций до подтверждения полноты. + +#### Scenario: Обновление журнала временно не удалось +- **WHEN** для применённого периода и страницы уже есть записи, а обновление завершается временной ошибкой +- **THEN** записи и параметры просмотра сохраняются без предупреждения и кнопки повтора до автоматического восстановления + +#### Scenario: Следующая страница ещё не получена +- **WHEN** пользователь перешёл к следующей странице, но её загрузка временно не удалась +- **THEN** область новой страницы показывает ожидание или нейтральную недоступность, не выдаёт прежние записи за новую страницу и восстанавливает чтение автоматически + ### Requirement: Full ledger presents compact rows Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для каждой записи денежное изменение в исходной валюте MUST сопровождаться подписью конкретного типа события и знаком: для купонной операции — «Купон», для дивидендной — «Дивиденд», для остальных записей — подпись их категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Строка MUST NOT повторять подпись типа события во вторичной метадате записи. Знак «+» / «−» и семантический цвет MUST оставаться носителями направления и MUST NOT заменяться только текстом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество. diff --git a/openspec/specs/brokerage-account-upcoming-events/spec.md b/openspec/specs/brokerage-account-upcoming-events/spec.md index 83f4ad4..6fb2fb7 100644 --- a/openspec/specs/brokerage-account-upcoming-events/spec.md +++ b/openspec/specs/brokerage-account-upcoming-events/spec.md @@ -64,27 +64,49 @@ Instrument в Future Cash Event MUST иметь тот же доменный с - **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает запрос ошибкой ### Requirement: Account page presents an upcoming events list -Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника. + +Страница Brokerage Account MUST предоставлять действие перехода на отдельную +страницу «Будущие события». Эта страница MUST показывать все полученные события +компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, +инструментом и прогнозной суммой в исходной валюте, если она известна. Страница +MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может +быть неполным из-за доступности данных источника. Это предметное ограничение +прогноза MUST NOT превращаться в отчёт о технической синхронизации. + +При временном сбое обновления страница MUST сохранять ранее доступный результат +той же выборки без технических сообщений, времени получения и ручного повтора. +При отсутствии результата SHALL показываться первичное ожидание либо нейтральная +недоступность с автоматическим восстановлением по `frontend-data-resilience`. +Подтверждённая потеря доступа MUST скрывать личные данные независимо от наличия +сохранённого результата. #### Scenario: Просмотр ближайших событий - **WHEN** User открывает «Будущие события» из обзора Brokerage Account - **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы #### Scenario: Пустое состояние страницы -- **WHEN** запрос будущих событий успешно возвращает пустой список -- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет ограничения покрытия источника +- **WHEN** запрос будущих событий успешно возвращает полный пустой список +- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника #### Scenario: Ошибка страницы -- **WHEN** запрос будущих событий завершается ошибкой -- **THEN** страница не показывает частичный или устаревший список и предоставляет действие повторить запрос +- **WHEN** запрос будущих событий завершается временной ошибкой +- **THEN** страница сохраняет доступный результат той же выборки либо показывает нейтральную недоступность без ручного повтора, восстанавливая чтение автоматически и не выдавая частичные данные за полный прогноз ### Requirement: Account overview includes upcoming events -Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event и действие перехода на полную страницу «Будущие события». Неуспешный или пустой запрос этого блока MUST оставаться локальным состоянием блока и не заменять успешно отображённый Portfolio. + +Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash +Event и действие перехода на полную страницу «Будущие события». Неуспешный +или пустой запрос этого блока MUST оставаться локальным состоянием блока и +не заменять успешно отображённый Portfolio. При временном сбое обновления +блок MUST сохранять доступные события без технического предупреждения; при +отсутствии результата SHALL показываться нейтральная недоступность после +первичного ожидания. Восстановление MUST выполняться автоматически без ручного +повтора и без подмены неизвестного результата подтверждённой пустотой. #### Scenario: Отображение ближайших событий - **WHEN** обзор счёта успешно загружен и календарь содержит будущие события - **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице #### Scenario: Ближайшие события недоступны -- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается ошибкой -- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственную ошибку +- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается временной ошибкой +- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние события либо показывает нейтральную недоступность без технической ошибки и ручного повтора diff --git a/openspec/specs/brokerage-accounts/spec.md b/openspec/specs/brokerage-accounts/spec.md index 7f74a4a..476bdd3 100644 --- a/openspec/specs/brokerage-accounts/spec.md +++ b/openspec/specs/brokerage-accounts/spec.md @@ -57,16 +57,38 @@ - **THEN** система отвечает `503` Problem Details с code `TBANK_UNAVAILABLE` без счетов ### Requirement: Экран списка брокерских счетов -Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Клиент MUST использовать успешный frontend-кэш списка в течение 60 секунд. После истечения этого срока клиент MUST запрашивать актуальный список; при уже имеющемся успешном списке он MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового обновления и после его ошибки, ясно отделяя это состояние от свежего результата. При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный список широких интерактивных карточек счетов с локализованными типом и, если доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип, имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта, вести на `/accounts/:accountId` по всей области и сохранять доступное клавиатурное состояние. После успешной замены или удаления T-Bank connection клиент MUST инвалидировать список счетов. -Для доступной `portfolioSummary` карточка MUST показывать не более двух Money-итогов в исходных валютах и обозначать наличие остальных валют, если они есть; она MUST NOT показывать или подразумевать FX-конвертацию, межвалютный агрегированный итог либо Cash Balance как отдельный второй итог. При `portfolioSummary: null` карточка MUST показывать локальное безопасное состояние временной недоступности сводки, сохраняя метаданные счёта и возможность открыть его. При доступной пустой сводке карточка MUST не выдавать отсутствие итогов за временную ошибку. +Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` +без ручного обновления. Клиент MUST использовать успешный frontend-кэш списка +в течение 60 секунд. После истечения этого срока открытие или возвращение +на экран SHALL инициировать получение актуального списка; имеющийся Client +Cached Snapshot MUST оставаться видимым во время обновления и после временной +ошибки без технических статусов, времени получения и действий повтора. +Автоматическое восстановление SHALL соответствовать `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** клиент получает успешный пустой массив +- **WHEN** клиент получает успешный полный пустой массив - **THEN** клиент показывает штатное сообщение об отсутствии инвестиционных счетов T-Bank #### Scenario: Список берётся из свежего frontend-кэша @@ -75,11 +97,11 @@ #### Scenario: Устаревший список загружается повторно - **WHEN** User открывает список после истечения 60 секунд frontend-кэша и уже существует последний успешный список -- **THEN** клиент оставляет этот список видимым во время нового запроса и ясно обозначает фоновое обновление +- **THEN** клиент оставляет этот список видимым во время нового запроса без индикации фонового обновления #### Scenario: Ошибка фонового обновления не скрывает snapshot -- **WHEN** обновление устаревшего списка завершается ошибкой при наличии последнего успешного Client Cached Snapshot -- **THEN** клиент сохраняет snapshot видимым, безопасно сообщает о неуспешном обновлении и предоставляет применимое действие повторной загрузки +- **WHEN** обновление устаревшего списка завершается временной ошибкой при наличии последнего успешного Client Cached Snapshot +- **THEN** клиент сохраняет snapshot видимым без предупреждения и ручного повтора и восстанавливает получение данных автоматически #### Scenario: Карточка ограничивает валютные итоги - **WHEN** доступная сводка счёта содержит итоги в трёх или более валютах @@ -87,35 +109,49 @@ #### Scenario: Недоступность сводки локальна карточке - **WHEN** клиент получает список, в котором `portfolioSummary` одного счёта равна `null` -- **THEN** он показывает временную недоступность только на этой карточке, не скрывает остальные карточки и сохраняет переход на детали недоступной сводки счёта +- **THEN** он показывает нейтральную недоступность только этой сводки, не скрывает остальные карточки и сохраняет переход на детали счёта #### Scenario: Пустая сводка не является ошибкой - **WHEN** клиент получает доступную `portfolioSummary` с пустым `totalsByCurrency` -- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной недоступности +- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной ошибки ### Requirement: Клиентские состояния ошибки списка счетов -Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. Если последний успешный Client Cached Snapshot отсутствует, эти состояния MUST NOT показывать список; если snapshot существует во время фонового обновления, клиент MUST сохранить его видимым и показать соответствующее безопасное предупреждение и только применимые действия. + +Клиент 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** клиент не выполняет дополнительные запросы, пока User не выберет действие повторной загрузки +- **WHEN** запрос списка завершается подтверждённой ошибкой подключения или доступа, требующей действия пользователя +- **THEN** клиент не выполняет автоматических повторов и предлагает соответствующее действие вместо технической повторной загрузки #### Scenario: Token требует внимания - **WHEN** клиент получает `INVALID_TBANK_TOKEN` -- **THEN** клиент не показывает список и предлагает открыть настройки T-Bank +- **THEN** клиент скрывает личный список и предлагает восстановить подключение в настройках T-Bank #### Scenario: Временная ошибка T-Bank -- **WHEN** клиент получает `TBANK_UNAVAILABLE` -- **THEN** клиент не показывает список и предлагает повторить загрузку либо открыть настройки T-Bank +- **WHEN** клиент получает `TBANK_UNAVAILABLE` без доступного результата списка +- **THEN** клиент автоматически восстанавливает получение и после первичного ожидания показывает нейтральную недоступность без повторной загрузки и предложения перенастроить подключение #### Scenario: Неизвестная ошибка загрузки -- **WHEN** клиент получает ошибку без известного кода списка счетов -- **THEN** клиент не показывает список и предлагает только повторить загрузку +- **WHEN** клиент получает ошибку без известного кода списка счетов и без доступного результата +- **THEN** клиент показывает безопасное нейтральное состояние без технического сообщения или кнопки повтора, применяя только допустимое ограниченное восстановление #### Scenario: Известная ошибка при сохранённом snapshot - **WHEN** фоновое обновление списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE` -- **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить загрузку либо открыть настройки T-Bank +- **THEN** клиент сохраняет snapshot видимым без предупреждения, времени получения и ручных действий восстановления diff --git a/openspec/specs/frontend-data-resilience/spec.md b/openspec/specs/frontend-data-resilience/spec.md index f0193c0..8e98ea8 100644 --- a/openspec/specs/frontend-data-resilience/spec.md +++ b/openspec/specs/frontend-data-resilience/spec.md @@ -9,28 +9,125 @@ ## Requirements ### Requirement: Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio -Frontend MUST сохранять Client Cached Snapshot списка Brokerage Accounts или -Portfolio на экране, если для этого же набора данных уже был успешно получен -ответ. Начальная загрузка без такого ответа может показывать блокирующее -состояние загрузки. Client Cached Snapshot MUST оставаться видимым во время -фонового обновления и после его ошибки; он не является server-side fallback и MUST NOT -изменять или интерпретировать поле `Portfolio.isStale`. + +Frontend MUST сохранять последний успешный результат списка Brokerage Accounts, +Portfolio и других экранов чтения при фоновом обновлении и временной ошибке +для того же пользователя, подключения и набора данных. Подтверждённый пустой +результат MUST также считаться успешным. Смена счёта, инструмента, поискового +запроса, страницы или применённого периода MUST NOT представлять данные прежней +выборки как результат новой. + +Доступный результат MUST оставаться видимым без предупреждений о свежести, +времени получения, индикаторов фонового обновления и действий ручного повтора. +Frontend MUST NOT подменять внутренние признаки свежести и полноты, успешное +время источника или ошибку обновления только ради отсутствия их отображения. +Client Cached Snapshot MUST оставаться отличным от server-side fallback; +ошибка чтения MUST NOT изменять `Portfolio.isStale`. + +Обновление MUST сохранять выбранные фильтры, период, открытые разделы, ввод, +фокус и положение просмотра, пока соответствующее содержимое существует. +При logout, смене подключения или подтверждённом отказе доступа личные данные +прежней границы MUST перестать отображаться; поздний результат прежней загрузки +MUST NOT возвращать их на экран. #### Scenario: Список Brokerage Accounts обновляется в фоне -- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается - фоновое обновление того же списка -- **THEN** frontend оставляет список видимым и показывает только - неблокирующий индикатор обновления +- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается фоновое обновление того же списка +- **THEN** frontend оставляет список видимым без индикации обновления, времени получения и технических действий #### Scenario: Обновление Portfolio завершается ошибкой после успешного ответа -- **WHEN** экран Brokerage Account уже показывает Client Cached Snapshot - Portfolio, а последующее обновление этого же Portfolio завершается ошибкой -- **THEN** frontend сохраняет 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 отображает соответствующее server-side либо client-side - предупреждение независимо, не представляя один вид состояния другим +- **WHEN** backend возвращает Portfolio с `isStale` либо фоновое обновление Client Cached Snapshot завершается временной ошибкой +- **THEN** frontend сохраняет доступные данные без технических сообщений и не подменяет один внутренний вид состояния другим + +#### Scenario: Подтверждённая пустота переживает фоновую ошибку +- **WHEN** текущая выборка ранее успешно вернула полный пустой результат, а её обновление временно не удалось +- **THEN** подтверждённое пустое состояние сохраняется без предупреждения и не заменяется первичной загрузкой + +#### Scenario: Изменились параметры выборки +- **WHEN** пользователь применил другой период или открыл другой счёт, а данных новой выборки ещё нет +- **THEN** экран показывает ожидание или недоступность новой выборки и не подписывает прежние суммы новым периодом или счётом + +#### Scenario: Обновление не сбрасывает работу пользователя +- **WHEN** пользователь раскрыл раздел, выбрал фильтры или вводит данные, а фоновое чтение завершилось +- **THEN** обновление не сбрасывает эти действия, не переносит фокус и не возвращает пользователя в начало сохранившегося содержимого + +#### Scenario: Изменился доступ к личным данным +- **WHEN** пользователь вышел, успешно сменил подключение либо получил подтверждённый отказ доступа к текущим данным +- **THEN** прежние личные данные скрываются, а позднее завершение старой загрузки не восстанавливает их + +### 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** пользователь видит готовый результат без сообщения о догрузке остальной истории diff --git a/openspec/specs/frontend-loading-presentation/spec.md b/openspec/specs/frontend-loading-presentation/spec.md index b7a9496..802bee7 100644 --- a/openspec/specs/frontend-loading-presentation/spec.md +++ b/openspec/specs/frontend-loading-presentation/spec.md @@ -59,9 +59,11 @@ MUST NOT быть основным loading-состоянием. Frontend MUST сохранять уже доступное содержимое или элемент управления основным контекстом, когда выполняется пользовательское действие либо фоновое -обновление Client Cached Snapshot. В этих состояниях frontend SHALL показывать -компактный progress indicator рядом с соответствующим контекстом и MUST NOT -заменять этот контекст skeleton placeholders. +обновление Client Cached Snapshot. Явное действие пользователя SHALL иметь +компактную индикацию ожидания рядом с соответствующим элементом управления. +Фоновое обновление при доступном результате MUST проходить без индикаторов, +технических сообщений и объявлений о ходе работы. В обоих случаях frontend +MUST NOT заменять уже доступный контекст skeleton placeholders. #### Scenario: Выполняется пользовательское действие @@ -71,4 +73,39 @@ Frontend MUST сохранять уже доступное содержимое #### Scenario: Обновляется уже показанный snapshot - **WHEN** frontend обновляет данные при наличии видимого Client Cached Snapshot -- **THEN** frontend оставляет snapshot видимым, показывает компактную индикацию фонового обновления и не заменяет snapshot skeleton placeholders +- **THEN** frontend оставляет snapshot видимым без индикации фонового обновления и без замены snapshot skeleton placeholders + +### 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** состояние недоступности ограничено этим блоком и не заменяет доступное содержимое страницы diff --git a/openspec/specs/frontend-ui-resilience/spec.md b/openspec/specs/frontend-ui-resilience/spec.md index a64433c..752e66c 100644 --- a/openspec/specs/frontend-ui-resilience/spec.md +++ b/openspec/specs/frontend-ui-resilience/spec.md @@ -25,12 +25,20 @@ Frontend SHALL сбрасывать состояние route error после п - **THEN** frontend сбрасывает предыдущую runtime-ошибку и отображает целевой route ### Requirement: Ожидаемые finance states не становятся route failures -Frontend SHALL сохранять локальное отображение и recovery actions известных состояний Brokerage Account, Portfolio и T-Bank connection. Эти состояния MUST NOT заменяться глобальным fallback. + +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 +- **WHEN** frontend получает устаревший Portfolio Snapshot в действующей границе доступа +- **THEN** frontend отображает Portfolio без предупреждения об устаревании и без глобального fallback #### Scenario: Ошибка T-Bank connection - **WHEN** запрос T-Bank connection завершается известной ошибкой -- **THEN** frontend показывает существующее локальное состояние ошибки и доступное для него recovery action вместо глобального fallback +- **THEN** frontend сохраняет локальное состояние: временный сбой чтения восстанавливается автоматически, а подтверждённая необходимость действия пользователя сопровождается этим действием вместо глобального fallback diff --git a/openspec/specs/frontend-user-feedback/spec.md b/openspec/specs/frontend-user-feedback/spec.md new file mode 100644 index 0000000..1ed8582 --- /dev/null +++ b/openspec/specs/frontend-user-feedback/spec.md @@ -0,0 +1,114 @@ +# frontend-user-feedback Specification + +## Purpose + +Определяет спокойную и понятную обратную связь во всех разделах WealthLedger: +пользователь видит финансовые данные и результат своих действий, а не состояние +внутреннего процесса получения данных. + +## 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** приложение не объявляет успех и не повторяет изменение автоматически как обычную загрузку данных diff --git a/openspec/specs/portfolio-view/spec.md b/openspec/specs/portfolio-view/spec.md index a234991..e5ddea9 100644 --- a/openspec/specs/portfolio-view/spec.md +++ b/openspec/specs/portfolio-view/spec.md @@ -117,105 +117,84 @@ Portfolio Snapshot является результатом наблюдения ### Requirement: Состояния и ошибки Portfolio на клиенте Клиент SHALL загружать Portfolio при открытии защищённого маршрута -`/accounts/:accountId`. Он MUST использовать успешный ответ в течение -30 секунд, без automatic retry, polling или refetch при возврате фокуса. -При загрузке без ранее полученного успешного ответа для этого же Brokerage -Account клиент MUST показывать структурное skeleton-состояние ожидаемого -содержимого, включая сводку, без вымышленных сумм. Область загрузки MUST -иметь доступное обозначение занятости; skeleton MUST NOT быть интерактивным -или получать клавиатурный фокус. +`/accounts/:accountId` и использовать успешный ответ в течение 30 секунд. +Временные сбои и возвращение к экрану с требующими обновления данными SHALL +обрабатываться автоматическим восстановлением по `frontend-data-resilience`. +Клиент MUST NOT предоставлять ручное обновление или повторную загрузку. -Истечение срока свежести MUST NOT само по себе скрывать уже доступный -Client Cached Snapshot. Во время фонового обновления и после его ошибки -клиент MUST сохранять ранее полученные данные того же Portfolio, включая -валютные итоги и исходное `lastSuccessfulAt`. При обновлении клиент MUST -показывать компактную неблокирующую индикацию без замены данных skeleton. -При ошибке обновления он MUST показывать локализованное неблокирующее -предупреждение о последних полученных данных и предоставлять действие -повторной попытки. Сохранённые данные MUST NOT считаться новым успешным -снимком или подтверждением текущей доступности Brokerage Account. +При первичной загрузке без успешного результата того же Brokerage Account +клиент MUST показывать структурный skeleton ожидаемого содержимого, включая +сводку, без вымышленных сумм. Область MUST иметь доступное обозначение занятости; +skeleton MUST NOT быть интерактивным или получать фокус. Неуспешное или +затянувшееся первичное получение SHALL переходить в локальную недоступность +по `frontend-loading-presentation`, а не в бесконечную загрузку. -Клиент MUST показать штатное empty-состояние только при пустых `positions` -и `cashBalances`, без нулевых валютных карточек. Для показываемого состава -Portfolio клиент MUST сохранять время успешного получения снимка -`lastSuccessfulAt`, форматируемое в UTC. При `isStale: true` клиент MUST -показывать Snapshot, его исходное время и нейтральное предупреждение -о временной недоступности T-Bank. Это предупреждение само по себе MUST NOT -добавлять действие ручного обновления. Server-side stale и ошибка фонового -обновления Client Cached Snapshot MUST оставаться раздельными состояниями: -ошибка клиента MUST NOT менять или подменять `isStale`, а оба предупреждения -и действие повтора клиентского обновления MUST оставаться доступными при -одновременном наличии этих состояний. +Истечение срока свежести MUST NOT скрывать доступный Client Cached Snapshot. +При обновлении и временной ошибке клиент MUST сохранять данные того же +Portfolio, включая валютные итоги, без индикации фоновой работы, предупреждений, +времени получения и уведомлений. Исходное `lastSuccessfulAt` и `isStale` MUST +сохранять внутренний смысл: скрытие этих сведений MUST NOT делать сохранённый +снимок новым успешным результатом или подтверждением текущего доступа. +Server-side stale и ошибка клиентского чтения MUST NOT подменять друг друга. -При отсутствии ранее успешно полученного Client Cached Snapshot этого же -Portfolio и ответе `404` клиент MUST показать состояние недоступного счёта -с действием возврата к списку счетов без действия повтора. При отсутствии -Snapshot и `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, `TBANK_UNAVAILABLE` -или иной ошибке клиент MUST показать безопасное error-состояние с доступными -для ошибки действиями; действие повтора разрешено только для временной -и неизвестной ошибки. +Клиент 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** клиент показывает позиции, остатки, валютные итоги, время - последнего успешного снимка и нейтральное предупреждение; само это - предупреждение не добавляет действие ручного обновления +- **WHEN** клиент получает успешный ответ с `isStale: true` в действующей границе доступа +- **THEN** клиент показывает позиции, остатки и доступные валютные итоги без времени последнего снимка, предупреждения и ручного обновления #### Scenario: Недоступный счёт на клиенте -- **WHEN** для текущего Portfolio отсутствует ранее успешно полученный - Client Cached Snapshot и клиент получает `404` -- **THEN** клиент не показывает Portfolio и предлагает вернуться к списку - Brokerage Account без действия повтора +- **WHEN** клиент получает подтверждённый `404` текущего Portfolio +- **THEN** клиент не показывает личный Portfolio, включая ранее сохранённый, и предлагает вернуться к списку Brokerage Account без действия повтора #### Scenario: Первичная загрузка Portfolio -- **WHEN** клиент ожидает Portfolio этого Brokerage Account без ранее - полученного успешного ответа -- **THEN** он показывает структурный skeleton, включая место сводки, - с доступным обозначением загрузки, без вымышленных или нулевых сумм - и без фокусируемых placeholders +- **WHEN** клиент ожидает Portfolio этого Brokerage Account без ранее полученного успешного ответа +- **THEN** он показывает структурный skeleton, включая место сводки, с доступным обозначением загрузки, без вымышленных или нулевых сумм и без фокусируемых placeholders #### Scenario: Обновление после истечения срока свежести -- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое - обновление данных того же Brokerage Account -- **THEN** клиент сохраняет прежние валютные итоги и время их успешного - получения, показывает неблокирующую индикацию обновления и не заменяет - доступный состав и сводку skeleton-состоянием +- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое обновление данных того же Brokerage Account +- **THEN** клиент сохраняет прежние валютные итоги без индикации обновления и не заменяет доступный состав и сводку skeleton-состоянием #### Scenario: Ошибка обновления после успешного ответа -- **WHEN** клиент уже показывает Client Cached Snapshot Portfolio, - а обновление этих же данных завершается ошибкой -- **THEN** клиент сохраняет данные, суммы и время прежнего снимка, - показывает неблокирующее предупреждение и действие повтора; - ошибка не делает сохранённый снимок свежим и не изменяет `isStale` +- **WHEN** клиент уже показывает Client Cached Snapshot Portfolio, а обновление этих же данных завершается временной ошибкой +- **THEN** клиент сохраняет данные и суммы без предупреждения и действия повтора; ошибка не делает снимок свежим и не изменяет `isStale` #### Scenario: Ошибка клиентского обновления при server-side stale -- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое - обновление этого же Portfolio завершается ошибкой -- **THEN** прежние суммы и время остаются видимыми одновременно - с нейтральным server-side предупреждением, отдельным предупреждением - об ошибке клиентского обновления и его действием повтора +- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое обновление этого же Portfolio завершается временной ошибкой +- **THEN** прежние суммы остаются видимыми без технических предупреждений и ручных действий, сохраняя различие внутренних состояний #### Scenario: Повторное обновление успешно -- **WHEN** после ошибки фонового обновления User запускает повторную - попытку и получает успешный Portfolio Snapshot -- **THEN** до ответа прежние суммы и время остаются видимыми, затем - клиент показывает значения и `lastSuccessfulAt` успешного ответа, - убирает предупреждение об ошибке клиентского обновления и показывает - server-side предупреждение только в соответствии с новым `isStale` +- **WHEN** после временной ошибки автоматическое восстановление получает успешный Portfolio Snapshot +- **THEN** до ответа прежние суммы остаются видимыми, затем клиент показывает новый результат без уведомления об обновлении и без переноса пользовательского фокуса #### Scenario: Штатный пустой Portfolio на клиенте -- **WHEN** успешно полученный Portfolio содержит пустые `positions` - и `cashBalances` -- **THEN** клиент показывает существующее empty-состояние без валютных - карточек с вымышленными нулевыми итогами +- **WHEN** успешно полученный полный Portfolio содержит пустые `positions` и `cashBalances` +- **THEN** клиент показывает существующее empty-состояние без валютных карточек с вымышленными нулевыми итогами + +#### Scenario: Первичный Portfolio пока недоступен + +- **WHEN** данных Portfolio нет, а первичное получение не удалось или превысило ограниченное время ожидания +- **THEN** клиент показывает нейтральную недоступность без кнопки повтора и автоматически заменяет её содержимым после успешного восстановления + +#### Scenario: Требуется восстановить подключение + +- **WHEN** клиент получает `TBANK_NOT_CONNECTED` или `INVALID_TBANK_TOKEN` при наличии сохранённого личного Portfolio +- **THEN** личные данные скрываются, а клиент предлагает необходимое действие подключения без технической повторной загрузки ### Requirement: Представление карточек позиций на клиенте Клиент MUST отображать `STOCK` и `BOND` отдельными секциями. Внутри каждой секции MUST сохраняться upstream-порядок позиций. Позиции иных типов MUST оставаться доступными в отдельной общей секции. diff --git a/openspec/specs/tbank-connection/spec.md b/openspec/specs/tbank-connection/spec.md index eaba473..ccf0d0a 100644 --- a/openspec/specs/tbank-connection/spec.md +++ b/openspec/specs/tbank-connection/spec.md @@ -66,16 +66,45 @@ - **THEN** система сохраняет подключение и отвечает текущим статусом ### Requirement: Управление подключением в настройках -Клиент SHALL показывать в настройках статус T-Bank connection, текст о необходимости read-only T-Bank Invest token, поле token и действие подключения либо замены. Для существующего подключения клиент MUST показывать действие удаления с modal-подтверждением. Во время создания, замены или удаления все действия секции подключения и действия modal MUST быть отключены. После успешного создания или замены клиент MUST очистить поле token и обновить статус; при ошибке token MUST остаться в поле. + +Клиент 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** клиент показывает отдельное error-состояние с возможностью повторить запрос и не показывает форму подключения +- **WHEN** клиент временно не может получить статус подключения +- **THEN** он автоматически восстанавливает чтение, сохраняет известный статус текущей сессии либо показывает нейтральную недоступность после первичного ожидания без формы и кнопки повтора #### Scenario: Ошибка проверки token в клиенте - **WHEN** создание или замена получает `INVALID_TBANK_TOKEN` либо `TBANK_UNAVAILABLE` -- **THEN** клиент показывает соответственно локализованное сообщение о token и правах либо о временной недоступности T-Bank, возвращает интерактивность и сохраняет введённый token +- **THEN** клиент показывает соответственно локализованное сообщение о token и правах либо о временной недоступности T-Bank в контексте формы, возвращает интерактивность и сохраняет введённый token + +#### Scenario: Фоновое чтение не сбрасывает ввод +- **WHEN** пользователь редактирует token в доступной секции, а фоновое чтение статуса временно не удалось +- **THEN** ввод и доступная секция сохраняются без технического предупреждения или уведомления + +#### Scenario: Удаление не удалось +- **WHEN** пользователь подтвердил удаление подключения, а действие завершилось ошибкой +- **THEN** клиент не показывает успех, сохраняет известный статус и локальную ошибку, возвращает применимые действия и не повторяет удаление автоматически