From c5ec117328c0fce9f053f520bb7100a7a3b973ec Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 17 Oct 2026 22:16:29 +0300 Subject: [PATCH] spec: archive quiet-user-feedback and sync canonical specs (#50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native archive применил 9 added и 12 modified требований без удалений. Все 10 сценариев, которые удалили бы исходные MODIFIED-блоки, сохранены. Новый canonical spec frontend-user-feedback внесён в AI_INDEX. Completion Evidence фиксирует расхождение отметок задач 8.1-8.4 с фактом и его устранение в WealthLedger/docs#51. --- AI_INDEX.md | 1 + .../.openspec.yaml | 0 .../completion-evidence.md | 70 +++++++++ .../2026-10-17-quiet-user-feedback}/design.md | 0 .../proposal.md | 0 .../spec.md | 0 .../specs/brokerage-account-ledger/spec.md | 0 .../brokerage-account-upcoming-events/spec.md | 0 .../specs/brokerage-accounts/spec.md | 0 .../specs/frontend-data-resilience/spec.md | 0 .../frontend-loading-presentation/spec.md | 0 .../specs/frontend-ui-resilience/spec.md | 0 .../specs/frontend-user-feedback/spec.md | 0 .../specs/portfolio-view/spec.md | 0 .../specs/tbank-connection/spec.md | 0 .../2026-10-17-quiet-user-feedback}/tasks.md | 0 .../spec.md | 67 +++++++-- .../specs/brokerage-account-ledger/spec.md | 38 ++++- .../brokerage-account-upcoming-events/spec.md | 80 +++++----- openspec/specs/brokerage-accounts/spec.md | 95 ++++++++---- .../specs/frontend-data-resilience/spec.md | 140 ++++++++++++++++-- .../frontend-loading-presentation/spec.md | 43 +++++- openspec/specs/frontend-ui-resilience/spec.md | 16 +- openspec/specs/frontend-user-feedback/spec.md | 113 ++++++++++++++ openspec/specs/portfolio-view/spec.md | 83 ++++++++--- openspec/specs/tbank-connection/spec.md | 37 ++++- 26 files changed, 654 insertions(+), 129 deletions(-) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/.openspec.yaml (100%) create mode 100644 openspec/changes/archive/2026-10-17-quiet-user-feedback/completion-evidence.md rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/design.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/proposal.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/brokerage-account-income-analytics/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/brokerage-account-ledger/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/brokerage-account-upcoming-events/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/brokerage-accounts/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/frontend-data-resilience/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/frontend-loading-presentation/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/frontend-ui-resilience/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/frontend-user-feedback/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/portfolio-view/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/specs/tbank-connection/spec.md (100%) rename openspec/changes/{quiet-user-feedback => archive/2026-10-17-quiet-user-feedback}/tasks.md (100%) create mode 100644 openspec/specs/frontend-user-feedback/spec.md diff --git a/AI_INDEX.md b/AI_INDEX.md index 76a3891..50879fe 100644 --- a/AI_INDEX.md +++ b/AI_INDEX.md @@ -30,6 +30,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/archive/2026-10-17-quiet-user-feedback/.openspec.yaml similarity index 100% rename from openspec/changes/quiet-user-feedback/.openspec.yaml rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/.openspec.yaml diff --git a/openspec/changes/archive/2026-10-17-quiet-user-feedback/completion-evidence.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/completion-evidence.md new file mode 100644 index 0000000..ed5fd16 --- /dev/null +++ b/openspec/changes/archive/2026-10-17-quiet-user-feedback/completion-evidence.md @@ -0,0 +1,70 @@ +# Completion Evidence — quiet-user-feedback + +Факты поставки, наблюдённые 2026-10-17 перед native archive. Все 31 task +отмечены выполненными; реализация и документация проверены по состоянию +`origin/develop` соответствующих репозиториев, а не по отметкам в `tasks.md`. + +Change координировался между тремя репозиториями. Delivery шла не в одном +потоке: `frontend` поставлен 2026-10-06, `specs` и `docs` отстали и были +завершены 2026-10-17 по `WealthLedger/specs#50` и `WealthLedger/docs#51`. + +## Delivery Map + +| Область | Поставленный scope | Коммит | +| --- | --- | --- | +| frontend | Тихое чтение: удалены ручное обновление и повтор на экранах просмотра, индикатор фоновой работы и предупреждение о синхронизации. Добавлены ограниченный retry GET и модель первичного ожидания. | `5b30bb8` | +| specs | Delta-спецификации согласованы с canonical specs после часовой синхронизации; новый spec `frontend-user-feedback`. | `6682f0c`, merge `5a7e319` | +| docs | ADR 021 о тихом чтении; `ui-resilience` и затронутые `domains/**` приведены к AS-IS. | merge `e8e3ef6` | + +## Нормативное поведение + +Решение определено по фактической реализации. Два независимых изменения +оказались совместимы и вместе образуют принятое поведение: + +- `frontend 5b30bb8` (2026-10-06) убрал индикацию собственного чтения клиента + и ручные действия повтора. +- `frontend 3c226f8` (2026-10-09, `WealthLedger/main#22`) добавил показ + состояния источника из `meta`: время последнего успеха, устаревание, исход + последней часовой попытки и неполноту. + +Запрет change относится к индикации собственного чтения клиента и ручному +повтору. Состояние источника — свойство данных, а не отчёт о техническом +процессе, и остаётся видимым. Ошибки явных действий пользователя сохраняются. + +## Проверки + +| Проверка | Результат | +| --- | --- | +| `npx openspec validate quiet-user-feedback --strict` | valid | +| `npx openspec archive quiet-user-feedback --yes` | `Totals: + 9, ~ 12, - 0, → 0` | +| Сохранность 10 сценариев, которые удалили бы исходные `MODIFIED`-блоки | 10/10 присутствуют в canonical specs после archive | +| specs `npm run verify`, `npm run check:openspec` | pass; `openspec 1.13.1 on Node 24.21.0` | +| docs `npm run verify` | exit 0, новый ADR охвачен gate | +| specs PR #51, docs PR #52 | по 3/3 required checks | + +Код-основание нормативного поведения, снятое с `frontend/origin/develop`: + +``` +retry actions в src/pages/** 0 +Button/onClick в SourceSyncStatus.tsx 0 +sourceTime + attempt renders 3 +GET_DEADLINE_MS 15_000 +RETRY_DELAYS_MS [1000, 2000] +INITIAL_WAIT_BUDGET_MS 10_000 +``` + +## Ограничения + +Отметки `[x]` задач 8.1–8.4 были проставлены 2026-10-06 без фактической работы +в `docs`: ADR отсутствовал, а `ui-resilience.md` описывал отменённое ручное +обновление. Расхождение обнаружено при подготовке archive и устранено в +`WealthLedger/docs#51`; archive до этого не выполнялся. `[x]` в `tasks.md` +этого change не является доказательством поставки. + +Исходные delta-блоки устарели относительно canonical specs и при archive +удалили бы 10 принятых сценариев. Они переписаны в `6682f0c` до archive. + +`frontend` gates в рамках завершения не перезапускались: реализация принята +в `develop` 2026-10-06 и работами 2026-10-17 не изменялась. Независимая QA +по schema `wealth-ledger-sdd` не требуется; semantic review выполнен по +фактическому коду и состоянию документации. diff --git a/openspec/changes/quiet-user-feedback/design.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/design.md similarity index 100% rename from openspec/changes/quiet-user-feedback/design.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/design.md diff --git a/openspec/changes/quiet-user-feedback/proposal.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/proposal.md similarity index 100% rename from openspec/changes/quiet-user-feedback/proposal.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/proposal.md diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-account-income-analytics/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-account-ledger/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-account-ledger/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/brokerage-account-ledger/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-account-ledger/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-account-upcoming-events/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/brokerage-accounts/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-accounts/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/brokerage-accounts/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/brokerage-accounts/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-data-resilience/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-data-resilience/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/frontend-data-resilience/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-data-resilience/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-loading-presentation/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-loading-presentation/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/frontend-loading-presentation/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-loading-presentation/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-ui-resilience/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-ui-resilience/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/frontend-ui-resilience/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-ui-resilience/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/frontend-user-feedback/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-user-feedback/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/frontend-user-feedback/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/frontend-user-feedback/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/portfolio-view/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/portfolio-view/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/portfolio-view/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/portfolio-view/spec.md diff --git a/openspec/changes/quiet-user-feedback/specs/tbank-connection/spec.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/tbank-connection/spec.md similarity index 100% rename from openspec/changes/quiet-user-feedback/specs/tbank-connection/spec.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/specs/tbank-connection/spec.md diff --git a/openspec/changes/quiet-user-feedback/tasks.md b/openspec/changes/archive/2026-10-17-quiet-user-feedback/tasks.md similarity index 100% rename from openspec/changes/quiet-user-feedback/tasks.md rename to openspec/changes/archive/2026-10-17-quiet-user-feedback/tasks.md diff --git a/openspec/specs/brokerage-account-income-analytics/spec.md b/openspec/specs/brokerage-account-income-analytics/spec.md index 0940e67..839618e 100644 --- a/openspec/specs/brokerage-account-income-analytics/spec.md +++ b/openspec/specs/brokerage-account-income-analytics/spec.md @@ -94,41 +94,64 @@ ### Requirement: Realized analytics never presents an incomplete result as complete -Система MUST формировать выбранный результат, его trend buckets, instrument breakdown и drill-down только после успешного получения всех необходимых cursor pages исполненных операций выбранного периода. Ошибка любой необходимой страницы MUST завершать расчёт восстанавливаемой ошибкой и MUST NOT возвращать частичные totals, buckets или breakdown как полный результат. Успешный период без подходящих выплат MUST возвращать нулевой результат и отображаться с явным объяснением пустого состояния. Система MUST NOT загружать другие периоды ради сравнения полученного дохода с выбранным периодом. +Система 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 со всех страниц выбранного периода без расчёта сравнений с другими периодами +- **THEN** система формирует результат по всем подходящим Ledger Entries со всех страниц #### 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: Обзор показывает доход текущего месяца - - **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 показывает собственное состояние восстановления +- **WHEN** Portfolio обзора успешно загружен, но расчёт current-month summary завершается временной ошибкой +- **THEN** Portfolio и остальные блоки остаются доступными, а summary сохраняет предыдущий результат той же выборки либо показывает нейтральную недоступность без ручного повтора ### Requirement: Income payments remain traceable to exact Ledger Entries @@ -196,3 +219,21 @@ - **WHEN** User переходит из выбранного Brokerage Account на существующую страницу «Будущие события» - **THEN** эта страница остаётся доступной с прежним поведением независимо от отсутствия прогноза на странице дохода + +### Requirement: Доход обновляется без технических сообщений + +Страница дохода MUST сохранять применённый период, черновик фильтров и +доступный результат при фоновом обновлении. Временная ошибка SHALL +восстанавливаться автоматически без кнопок обновления и повтора, времени +получения данных и уведомлений об их загрузке. Изменение периода MUST NOT +подписывать прежний результат новыми датами. Ошибки пользовательского периода +MUST оставаться у полей и MUST NOT запускать автоматический повтор ошибочного +ввода. Подтверждённая потеря доступа MUST скрывать личные результаты. + +#### Scenario: Доход обновляется при редактировании периода +- **WHEN** пользователь редактирует черновик дат, а фоновое обновление применённого периода завершилось +- **THEN** черновик и фокус сохраняются, результат относится к прежнему применённому периоду и техническое уведомление не появляется + +#### Scenario: Новый период пока недоступен +- **WHEN** пользователь применил другой допустимый период, но его расчёт ещё не получен +- **THEN** страница показывает ожидание или недоступность нового результата, не выдаёт прежние суммы за доход нового периода и восстанавливается автоматически diff --git a/openspec/specs/brokerage-account-ledger/spec.md b/openspec/specs/brokerage-account-ledger/spec.md index 91c620c..aba22ec 100644 --- a/openspec/specs/brokerage-account-ledger/spec.md +++ b/openspec/specs/brokerage-account-ledger/spec.md @@ -49,20 +49,25 @@ ### Requirement: Account overview includes recent ledger events -Обзор счёта MUST показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Каждая показанная запись MUST иметь ту же визуальную идентичность инструмента и семантику денежного движения, что и запись полного журнала. Исполненные купоны и дивиденды MUST иметь то же представление выплаты, что и на других экранах с купонами и дивидендами; остальные операции сохраняют представление записи журнала. Неуспешный или пустой запрос последних записей MUST оставаться локальным состоянием блока последних событий и MUST NOT заменять успешно отображённый обзор Portfolio. +Обзор счёта MUST показывать пять последних исполненных записей журнала этого +счёта и действие перехода к его полному журналу. Каждая запись MUST иметь ту же +визуальную идентичность инструмента и семантику денежного движения, что и запись +полного журнала. Неуспешный или пустой запрос MUST оставаться локальным состоянием +блока и MUST NOT заменять успешно отображённый Portfolio. При временном сбое +обновления блок MUST сохранять доступные записи без технического предупреждения +или ручного повтора. При отсутствии результата он SHALL показывать первичное +ожидание либо нейтральную недоступность, автоматически восстанавливая чтение. +Неподтверждённая пустота MUST NOT представляться отсутствием операций. #### Scenario: Отображение последних событий - - **WHEN** загружается обзор счёта и его журнал содержит исполненные операции -- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал, а купоны и дивиденды узнаваемы по тому же представлению выплаты +- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал #### Scenario: Последние записи недоступны - -- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой -- **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления +- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается временной ошибкой +- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние записи либо показывает нейтральную недоступность без технического повтора и восстанавливается автоматически #### Scenario: Последняя запись не содержит visual metadata инструмента - - **WHEN** одна из последних записей не имеет доступной presentation metadata инструмента - **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения @@ -153,3 +158,22 @@ #### Scenario: Ошибка загрузки - **WHEN** загрузка части отсутствующего периода завершается ошибкой - **THEN** ранее подтверждённые операции сохраняются, а неполная часть не помечается полной + +### Requirement: Журнал восстанавливает чтение без ручного обновления + +Страница журнала SHALL сохранять записи текущей выборки при временном сбое +обновления и восстанавливать чтение автоматически по `frontend-data-resilience`. +Период, категории и выбранная страница MUST сохраняться; результат другой +выборки MUST NOT подменять текущую. Страница MUST NOT показывать время получения, +технические статусы или действия ручного повтора. Даты операций, фильтры, +действия их применения и переходы по страницам MUST оставаться доступными. +При отсутствии результата SHALL применяться первичное ожидание и нейтральная +недоступность, а не сообщение об отсутствии операций до подтверждения полноты. + +#### Scenario: Обновление журнала временно не удалось +- **WHEN** для применённого периода и страницы уже есть записи, а обновление завершается временной ошибкой +- **THEN** записи и параметры просмотра сохраняются без предупреждения и кнопки повтора до автоматического восстановления + +#### Scenario: Следующая страница ещё не получена +- **WHEN** пользователь перешёл к следующей странице, но её загрузка временно не удалась +- **THEN** область новой страницы показывает ожидание или нейтральную недоступность, не выдаёт прежние записи за новую страницу и восстанавливает чтение автоматически diff --git a/openspec/specs/brokerage-account-upcoming-events/spec.md b/openspec/specs/brokerage-account-upcoming-events/spec.md index 81dab7f..6879b50 100644 --- a/openspec/specs/brokerage-account-upcoming-events/spec.md +++ b/openspec/specs/brokerage-account-upcoming-events/spec.md @@ -85,61 +85,75 @@ Instrument в Future Cash Event MUST иметь тот же доменный с ### Requirement: Account page presents an upcoming events list -Страница Brokerage Account MUST предоставлять read-only действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать события сохранённого полного прогноза компактным списком без фильтров, пагинации, суммарного итога или действия ручной синхронизации, с датой, типом, инструментом и прогнозной суммой в исходной валюте, если она известна. Купоны и дивиденды MUST использовать то же представление выплаты, что и исполненные купоны, дивиденды и события графика инструмента, но MUST быть явно обозначены как прогноз, а не полученные деньги. Дата дивиденда MUST быть обозначена как дата реестра. Страница MUST показывать `calculatedAt`, исходное окно прогноза и наблюдаемые freshness, полноту, время последнего успешного прогноза, последнюю часовую попытку и доступность из `meta` понятным текстом или иконкой с доступным именем, а не только цветом. Она MUST объяснять, что прогноз рассчитан по количеству бумаг на `calculatedAt`, а показанные события дополнительно ограничены запрошенным включительным UTC-окном; прогноз указан до налогов и может быть неполным из-за доступности данных источника. Открытие страницы и повтор HTTP-чтения MUST NOT запускать личную синхронизацию. +Страница Brokerage Account MUST предоставлять действие перехода на отдельную +страницу «Будущие события». Эта страница MUST показывать все полученные события +компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, +инструментом и прогнозной суммой в исходной валюте, если она известна. Страница +MUST объяснять, что прогноз основан на позиции счёта, указан до налогов и может +быть неполным из-за доступности данных источника. Это предметное ограничение +прогноза MUST NOT превращаться в отчёт о технической синхронизации. + +При временном сбое обновления страница MUST сохранять ранее доступный результат +той же выборки с его исходными `calculatedAt` и окном, без индикации +собственного чтения и ручного повтора. Состояние источника из `meta`, включая +неуспех последней часовой попытки и неполноту, MUST представляться по +`frontend-data-resilience`. При отсутствии результата SHALL показываться +первичное ожидание либо нейтральная недоступность с автоматическим +восстановлением по `frontend-data-resilience`. Подтверждённая потеря доступа +MUST скрывать личные данные независимо от наличия сохранённого результата. #### Scenario: Просмотр ближайших событий - - **WHEN** User открывает «Будущие события» из обзора Brokerage Account -- **THEN** он видит отсортированный read-only список событий сохранённого 30-календарного окна и `calculatedAt`, где купоны и дивиденды представлены как прогнозные выплаты, а offer обозначен без суммы +- **THEN** он видит отсортированный список событий ближайших 30 дней, где offer обозначен без суммы + +#### Scenario: Пустое состояние страницы +- **WHEN** запрос будущих событий успешно возвращает полный пустой список +- **THEN** страница сообщает, что на ближайшие 30 дней событий не найдено, и объясняет предметные ограничения покрытия источника + +#### Scenario: Ошибка страницы +- **WHEN** запрос будущих событий завершается временной ошибкой +- **THEN** страница сохраняет доступный результат той же выборки либо показывает нейтральную недоступность без ручного повтора, восстанавливая чтение автоматически и не выдавая частичные данные за полный прогноз #### Scenario: Прогнозный дивиденд с неизвестной суммой - - **WHEN** прогноз содержит дивиденд с известной датой реестра, но без суммы - **THEN** его карточка обозначает дату как дату реестра и сумму как неизвестную, не показывает нулевую или полученную выплату -#### Scenario: Пустое состояние страницы - -- **WHEN** запрос будущих событий успешно возвращает пустой список -- **THEN** страница сообщает, что в сохранённом окне прогноза событий не найдено, показывает `calculatedAt` и объясняет ограничения покрытия источника +#### Scenario: Ошибка обновления при сохранённом прогнозе на странице +- **WHEN** запрос будущих событий возвращает последний полный прогноз вместе с ошибкой его обновления +- **THEN** страница показывает этот прогноз с его исходными `calculatedAt` и окном и отдельно обозначает неуспех обновления источника, не предлагая ручной повтор #### Scenario: Первая загрузка либо ошибка без сохранённого прогноза - - **WHEN** запрос будущих событий возвращает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех -- **THEN** страница показывает доступное состояние загрузки либо ошибки без частичного списка и предоставляет при ошибке действие повторить только HTTP-чтение - -#### Scenario: Ошибка страницы - -- **WHEN** запрос будущих событий завершается ошибкой -- **THEN** страница не показывает частичный список и предоставляет действие повторить только HTTP-чтение - -#### Scenario: Ошибка обновления при сохранённом прогнозе на странице - -- **WHEN** запрос будущих событий возвращает последний полный прогноз вместе с ошибкой его обновления -- **THEN** страница показывает этот прогноз с его исходными `calculatedAt` и окном, отдельно сообщает об ошибке обновления и предоставляет действие повторить только HTTP-чтение +- **THEN** страница показывает доступное состояние первичного ожидания либо нейтральной недоступности без частичного списка и восстанавливает чтение автоматически ### Requirement: Account overview includes upcoming events -Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash Event сохранённого полного прогноза в применённом запрошенном окне, его `calculatedAt`, исходное окно, наблюдаемую freshness, полноту, последнюю часовую попытку и доступность из `meta`, а также действие перехода на read-only полную страницу «Будущие события». Купоны и дивиденды MUST быть представлены той же карточкой выплаты, что на полной странице событий и остальных экранах, с явным обозначением прогнозного статуса; погашение и оферта сохраняют своё значение. Статус MUST быть понятен текстом или иконкой с доступным именем, а не только цветом. Неуспешное обновление при сохранённом прогнозе MUST оставаться локальным состоянием блока, сохранять показанные события и их исходное окно и MUST NOT заменять успешно отображённый Portfolio. Если полного прогноза нет, неуспешное обновление MUST оставаться локальным состоянием блока без частичного списка. Обзор MUST NOT предоставлять действие ручной синхронизации; его повтор MUST выполнять только HTTP-чтение. +Обзор Brokerage Account MUST показывать не более пяти ближайших Future Cash +Event и действие перехода на полную страницу «Будущие события». Неуспешный +или пустой запрос этого блока MUST оставаться локальным состоянием блока и +не заменять успешно отображённый Portfolio. При временном сбое обновления +блок MUST сохранять доступные события сохранённого окна без индикации +собственного чтения; при отсутствии результата SHALL показываться нейтральная +недоступность после первичного ожидания. Состояние источника из `meta` MUST +представляться по `frontend-data-resilience`. Восстановление MUST выполняться +автоматически без ручного повтора и без подмены неизвестного результата +подтверждённой пустотой. #### Scenario: Отображение ближайших событий - -- **WHEN** обзор счёта успешно загружен и сохранённый прогноз содержит будущие события -- **THEN** обзор показывает первые пять событий в порядке даты, `calculatedAt` и действие перехода к полной странице, используя единое представление для прогнозных купонов и дивидендов - -#### Scenario: Ближайшие события недоступны без сохранённого прогноза - -- **WHEN** обзор счёта успешно загружен, а события возвращают `data: null` и `meta` обозначает выполнение первой загрузки либо её неуспех -- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственное доступное состояние загрузки либо ошибки без частичного списка +- **WHEN** обзор счёта успешно загружен и календарь содержит будущие события +- **THEN** обзор показывает первые пять событий в порядке даты и действие перехода к полной странице #### Scenario: Ближайшие события недоступны +- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается временной ошибкой +- **THEN** Portfolio остаётся доступным, а блок сохраняет прежние события либо показывает нейтральную недоступность без технической ошибки и ручного повтора -- **WHEN** обзор счёта успешно загружен, но запрос будущих событий завершается ошибкой -- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственную ошибку +#### Scenario: Ближайшие события недоступны без сохранённого прогноза +- **WHEN** обзор счёта успешно загружен, а события возвращают `data: null` и `meta` обозначает выполнение первой загрузки либо её неуспех +- **THEN** Portfolio остаётся доступным, а блок будущих событий отображает собственное доступное состояние первичного ожидания либо нейтральной недоступности без частичного списка #### Scenario: Неуспешное обновление не скрывает сохранённый прогноз - - **WHEN** обзор счёта получает сохранённый полный прогноз вместе с ошибкой его обновления -- **THEN** Portfolio и события сохранённого окна остаются доступными, а блок отдельно обозначает ошибку и предлагает повторить только HTTP-чтение +- **THEN** Portfolio и события сохранённого окна остаются доступными, а блок отдельно обозначает неуспех обновления источника, не предлагая ручной повтор ### Requirement: User can filter upcoming events by a forecast date range diff --git a/openspec/specs/brokerage-accounts/spec.md b/openspec/specs/brokerage-accounts/spec.md index f56a022..ced67a5 100644 --- a/openspec/specs/brokerage-accounts/spec.md +++ b/openspec/specs/brokerage-accounts/spec.md @@ -65,41 +65,66 @@ - **THEN** система сообщает о продолжающейся загрузке без частичного либо неподтверждённого списка ### Requirement: Экран списка брокерских счетов -Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Он MUST использовать успешный Client Cached Snapshot списка в течение 30 минут. Открытый экран Accounts MUST перечитывать локальный backend-результат каждые 30 минут и MUST NOT запускать импорт T-Bank. Клиент MUST показывать наблюдаемые freshness, полноту, время последнего успешного результата, последнюю часовую попытку и доступность из `meta` понятным текстом или иконкой с доступным именем, а не только цветом. При уже имеющемся успешном списке клиент MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового HTTP-перечитывания и после его ошибки, ясно отделяя это состояние от свежего backend-результата. При успешном непустом ответе клиент 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`. +Открытый экран MAY периодически перечитывать backend-результат, сохраняя +видимый snapshot, и MUST NOT создавать импорт T-Bank. Состояние источника из +`meta` MUST представляться по `frontend-data-resilience`. + +При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный +список широких интерактивных карточек счетов с локализованными типом и, если +доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип, +имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта, +вести на `/accounts/:accountId` по всей области и сохранять доступное +клавиатурное состояние. После успешной замены или удаления T-Bank connection +клиент MUST инвалидировать список счетов. + +Для доступной `portfolioSummary` карточка MUST показывать не более двух +Money-итогов в исходных валютах и обозначать наличие остальных валют, если +они есть; она MUST NOT показывать или подразумевать FX-конвертацию, +межвалютный агрегированный итог либо Cash Balance как отдельный второй итог. +При `portfolioSummary: null` карточка MUST показывать локальное нейтральное +состояние недоступности сводки, сохраняя метаданные счёта и возможность открыть +его. При доступной пустой сводке карточка MUST не выдавать отсутствие итогов +за временную ошибку. #### Scenario: Пользователь открывает счёт из списка - **WHEN** клиент успешно загрузил непустой список и User выбирает карточку счёта - **THEN** клиент переходит на `/accounts/:accountId` с `id` выбранного счёта #### Scenario: Список пуст -- **WHEN** клиент получает успешный пустой массив +- **WHEN** клиент получает успешный полный пустой массив - **THEN** клиент показывает штатное сообщение об отсутствии инвестиционных счетов T-Bank -#### Scenario: Последняя попытка неуспешна после полного списка -- **WHEN** клиент получает полный `data` списка и `meta` с неуспехом последней часовой попытки -- **THEN** клиент сохраняет список видимым, показывает его исходное время и текстовое либо доступно именованное предупреждение о неуспешной попытке - -#### Scenario: Первая загрузка списка выполняется или неуспешна -- **WHEN** клиент получает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех -- **THEN** клиент показывает соответственно доступное состояние загрузки либо ошибки без карточек счетов и без представления частичного списка как полного - #### Scenario: Список берётся из свежего frontend-кэша -- **WHEN** User повторно открывает список в течение 30 минут после успешной загрузки +- **WHEN** User повторно открывает список в течение 60 секунд после успешной загрузки - **THEN** клиент показывает сохранённые счета без нового API-запроса #### Scenario: Устаревший список загружается повторно -- **WHEN** User открывает список после истечения 30 минут frontend-кэша и уже существует последний успешный список -- **THEN** клиент оставляет этот список видимым во время нового HTTP-чтения и ясно обозначает фоновое обновление +- **WHEN** User открывает список после истечения 60 секунд frontend-кэша и уже существует последний успешный список +- **THEN** клиент оставляет этот список видимым во время нового запроса без индикации фонового обновления #### Scenario: Открытый экран перечитывает список - **WHEN** экран Accounts остаётся открытым 30 минут после последнего успешного HTTP-чтения - **THEN** клиент выполняет фоновое HTTP-перечитывание backend-результата, сохраняет видимый snapshot и не запускает импорт T-Bank +#### Scenario: Последняя попытка неуспешна после полного списка +- **WHEN** клиент получает полный `data` списка и `meta` с неуспехом последней часовой попытки +- **THEN** клиент сохраняет список видимым, показывает его исходное время и текстовое либо доступно именованное предупреждение о неуспешной попытке, не предлагая ручной повтор + +#### Scenario: Первая загрузка списка выполняется или неуспешна +- **WHEN** клиент получает `data: null`, а `meta` обозначает выполнение первой загрузки либо её неуспех +- **THEN** клиент показывает соответственно доступное состояние загрузки либо локальной недоступности без карточек счетов и без представления частичного списка как полного + #### Scenario: Ошибка фонового обновления не скрывает snapshot -- **WHEN** фоновое HTTP-перечитывание списка завершается ошибкой при наличии последнего успешного Client Cached Snapshot -- **THEN** клиент сохраняет snapshot видимым, безопасно сообщает о неуспешном HTTP-чтении и предоставляет применимое действие повторной загрузки +- **WHEN** обновление устаревшего списка завершается временной ошибкой при наличии последнего успешного Client Cached Snapshot +- **THEN** клиент сохраняет snapshot видимым без предупреждения и ручного повтора и восстанавливает получение данных автоматически #### Scenario: Карточка ограничивает валютные итоги - **WHEN** доступная сводка счёта содержит итоги в трёх или более валютах @@ -107,35 +132,49 @@ #### Scenario: Недоступность сводки локальна карточке - **WHEN** клиент получает список, в котором `portfolioSummary` одного счёта равна `null` -- **THEN** он показывает временную недоступность только на этой карточке, не скрывает остальные карточки и сохраняет переход на детали недоступной сводки счёта +- **THEN** он показывает нейтральную недоступность только этой сводки, не скрывает остальные карточки и сохраняет переход на детали счёта #### Scenario: Пустая сводка не является ошибкой - **WHEN** клиент получает доступную `portfolioSummary` с пустым `totalsByCurrency` -- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной недоступности +- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной ошибки ### Requirement: Клиентские состояния ошибки списка счетов -Клиент MUST NOT автоматически повторять неуспешное HTTP-чтение списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторного HTTP-чтения и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторного HTTP-чтения. Повтор MUST перечитывать backend-результат и MUST NOT запускать импорт T-Bank. Если последний успешный 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** HTTP-чтение списка завершается известной ошибкой -- **THEN** клиент не выполняет дополнительные запросы, пока User не выберет действие повторного HTTP-чтения +- **WHEN** запрос списка завершается подтверждённой ошибкой подключения или доступа, требующей действия пользователя +- **THEN** клиент не выполняет автоматических повторов и предлагает соответствующее действие вместо технической повторной загрузки #### Scenario: Token требует внимания - **WHEN** клиент получает `INVALID_TBANK_TOKEN` -- **THEN** клиент не показывает список и предлагает открыть настройки T-Bank +- **THEN** клиент скрывает личный список и предлагает восстановить подключение в настройках T-Bank #### Scenario: Временная ошибка T-Bank -- **WHEN** клиент получает `TBANK_UNAVAILABLE` -- **THEN** клиент не показывает список и предлагает повторить HTTP-чтение либо открыть настройки T-Bank +- **WHEN** клиент получает `TBANK_UNAVAILABLE` без доступного результата списка +- **THEN** клиент автоматически восстанавливает получение и после первичного ожидания показывает нейтральную недоступность без повторной загрузки и предложения перенастроить подключение #### Scenario: Неизвестная ошибка загрузки -- **WHEN** клиент получает ошибку без известного кода списка счетов -- **THEN** клиент не показывает список и предлагает только повторить HTTP-чтение +- **WHEN** клиент получает ошибку без известного кода списка счетов и без доступного результата +- **THEN** клиент показывает безопасное нейтральное состояние без технического сообщения или кнопки повтора, применяя только допустимое ограниченное восстановление #### Scenario: Известная ошибка при сохранённом snapshot -- **WHEN** фоновое HTTP-перечитывание списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE` -- **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить HTTP-чтение либо открыть настройки T-Bank +- **WHEN** фоновое обновление списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE` +- **THEN** клиент сохраняет snapshot видимым без предупреждения, времени получения и ручных действий восстановления diff --git a/openspec/specs/frontend-data-resilience/spec.md b/openspec/specs/frontend-data-resilience/spec.md index cf269b8..4ec3f28 100644 --- a/openspec/specs/frontend-data-resilience/spec.md +++ b/openspec/specs/frontend-data-resilience/spec.md @@ -9,20 +9,136 @@ ## Requirements ### Requirement: Client Cached Snapshot остаётся доступным при обновлении Accounts и Portfolio -Frontend MUST сохранять Client Cached Snapshot списка Brokerage Accounts или Portfolio на экране, если для того же набора данных уже был успешно получен ответ. Начальная загрузка без такого ответа может показывать блокирующее состояние загрузки. Для каждого личного resource response клиент MUST представлять freshness, полноту, время последнего успешного результата, последнюю часовую попытку и доступность из `meta` текстом либо доступно именованным элементом, а не только цветом; он MUST различать отсутствие попытки, выполнение, полный успех, частичный успех, неуспех, доступный пустой полный результат и отсутствие полного результата. Client Cached Snapshot MUST оставаться видимым во время фонового HTTP-перечитывания и после его ошибки; он не является server-side fallback и MUST NOT изменять или интерпретировать поле `Portfolio.isStale`. Повторное действие User MUST повторять только HTTP-чтение и MUST NOT запускать личную синхронизацию. Открытый инвестиционный экран MAY периодически перечитывать backend-результат, но MUST NOT создавать импорт T-Bank. + +Frontend MUST сохранять последний успешный результат списка Brokerage Accounts, +Portfolio и других экранов чтения при фоновом обновлении и временной ошибке +для того же пользователя, подключения и набора данных. Подтверждённый пустой +результат MUST также считаться успешным. Смена счёта, инструмента, поискового +запроса, страницы или применённого периода MUST NOT представлять данные прежней +выборки как результат новой. + +Доступный результат MUST оставаться видимым без индикаторов фонового чтения +клиента, технических предупреждений об этом чтении и действий ручного повтора. +Frontend MUST NOT подменять внутренние признаки свежести и полноты, успешное +время источника или ошибку обновления только ради отсутствия их отображения. +Client Cached Snapshot MUST оставаться отличным от server-side fallback; +ошибка чтения MUST NOT изменять `Portfolio.isStale`. + +Состояние источника из `meta` — время последнего успешного результата, признак +устаревания, исход последней часовой попытки и неполнота — MUST представляться +текстом либо доступно именованным элементом, а не только цветом, и MUST +различать отсутствие попытки, выполнение, полный успех, частичный успех и +неуспех. Это состояние принадлежит источнику данных, а не собственному чтению +клиента, и MUST NOT требовать от пользователя действия. + +Обновление MUST сохранять выбранные фильтры, период, открытые разделы, ввод, +фокус и положение просмотра, пока соответствующее содержимое существует. +При logout, смене подключения или подтверждённом отказе доступа личные данные +прежней границы MUST перестать отображаться; поздний результат прежней загрузки +MUST NOT возвращать их на экран. #### Scenario: Список Brokerage Accounts обновляется в фоне -- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается фоновое HTTP-перечитывание того же списка -- **THEN** frontend оставляет список видимым и показывает только неблокирующий индикатор перечитывания - -#### Scenario: Обновление Portfolio завершается ошибкой после успешного ответа -- **WHEN** экран Brokerage Account уже показывает Client Cached Snapshot Portfolio, а последующее HTTP-чтение завершается ошибкой -- **THEN** frontend сохраняет Portfolio видимым, показывает локализованное неблокирующее предупреждение и предоставляет повтор только HTTP-чтения - -#### Scenario: Server-side fallback и client cached snapshot различаются -- **WHEN** backend возвращает Portfolio с `isStale` либо HTTP-перечитывание Client Cached Snapshot завершается ошибкой -- **THEN** frontend отображает соответствующее server-side либо client-side предупреждение независимо, не представляя одно состояние другим +- **WHEN** экран Accounts уже показывает Client Cached Snapshot и начинается фоновое обновление того же списка +- **THEN** frontend оставляет список видимым без индикации собственного чтения и технических действий #### Scenario: Resource сохраняется после неуспешной часовой попытки - **WHEN** response содержит полный `data` и `meta`, обозначающий неуспех последней часовой попытки -- **THEN** frontend сохраняет `data` и его исходное время видимыми и отдельно обозначает неуспех попытки +- **THEN** frontend сохраняет `data` и его исходное время видимыми и отдельно обозначает неуспех попытки, не предлагая ручной повтор + +#### Scenario: Обновление Portfolio завершается ошибкой после успешного ответа +- **WHEN** экран Brokerage Account уже показывает Client Cached Snapshot Portfolio, а последующее обновление этого же Portfolio завершается временной ошибкой +- **THEN** frontend сохраняет Portfolio видимым без предупреждения и ручного повтора, а восстановление выполняется автоматически + +#### Scenario: Server-side fallback и client cached snapshot различаются +- **WHEN** backend возвращает Portfolio с `isStale` либо фоновое обновление Client Cached Snapshot завершается временной ошибкой +- **THEN** frontend сохраняет доступные данные без технических сообщений и не подменяет один внутренний вид состояния другим + +#### Scenario: Подтверждённая пустота переживает фоновую ошибку +- **WHEN** текущая выборка ранее успешно вернула полный пустой результат, а её обновление временно не удалось +- **THEN** подтверждённое пустое состояние сохраняется без предупреждения и не заменяется первичной загрузкой + +#### Scenario: Изменились параметры выборки +- **WHEN** пользователь применил другой период или открыл другой счёт, а данных новой выборки ещё нет +- **THEN** экран показывает ожидание или недоступность новой выборки и не подписывает прежние суммы новым периодом или счётом + +#### Scenario: Обновление не сбрасывает работу пользователя +- **WHEN** пользователь раскрыл раздел, выбрал фильтры или вводит данные, а фоновое чтение завершилось +- **THEN** обновление не сбрасывает эти действия, не переносит фокус и не возвращает пользователя в начало сохранившегося содержимого + +#### Scenario: Изменился доступ к личным данным +- **WHEN** пользователь вышел, успешно сменил подключение либо получил подтверждённый отказ доступа к текущим данным +- **THEN** прежние личные данные скрываются, а позднее завершение старой загрузки не восстанавливает их + +### 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..6e5e92e 100644 --- a/openspec/specs/frontend-loading-presentation/spec.md +++ b/openspec/specs/frontend-loading-presentation/spec.md @@ -59,16 +59,47 @@ 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: Выполняется пользовательское действие - - **WHEN** User запустил действие и его результат ещё ожидается - **THEN** frontend сохраняет соответствующий элемент управления или окружающий контекст видимым, показывает компактную индикацию процесса и не заменяет интерфейс skeleton placeholders #### 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..a1fff19 --- /dev/null +++ b/openspec/specs/frontend-user-feedback/spec.md @@ -0,0 +1,113 @@ +# 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 5939832..aa6673e 100644 --- a/openspec/specs/portfolio-view/spec.md +++ b/openspec/specs/portfolio-view/spec.md @@ -119,47 +119,86 @@ Portfolio Snapshot является результатом наблюдения - **THEN** новый состав становится текущим Portfolio Snapshot с неизвестной стоимостью позиции без подстановки нуля ### Requirement: Состояния и ошибки Portfolio на клиенте -Клиент SHALL загружать Portfolio при открытии защищённого маршрута `/accounts/:accountId`. Он MUST использовать успешный ответ в течение 30 секунд без automatic retry или refetch при возврате фокуса. Открытый экран MUST перечитывать сохранённый результат backend каждые 30 минут без запуска синхронизации T-Bank. Клиент MUST показывать freshness, полноту, время последнего успешного Snapshot, последнюю часовую попытку и доступность из `meta` текстом или доступно именованным элементом, а не только цветом. Клиент MUST NOT предоставлять действие ручной синхронизации Portfolio. При отсутствии ранее успешного ответа он MUST показывать структурный skeleton с доступным обозначением занятости без вымышленных сумм; skeleton MUST NOT быть интерактивным или получать клавиатурный фокус. -Истечение срока свежести MUST NOT скрывать Client Cached Snapshot. Во время фонового перечитывания и после его ошибки клиент MUST сохранять ранее полученные данные того же Portfolio, исходное `lastSuccessfulAt` и показывать неблокирующую индикацию либо предупреждение с действием повтора только HTTP-чтения. Сохранённые данные MUST NOT считаться новым успешным снимком или подтверждением текущей доступности Brokerage Account. При `isStale: true` клиент MUST показывать Snapshot, исходное время и нейтральное предупреждение без действия ручного обновления. Server-side stale и ошибка Client Cached Snapshot MUST оставаться раздельными состояниями. +Клиент SHALL загружать Portfolio при открытии защищённого маршрута +`/accounts/:accountId` и использовать успешный ответ в течение 30 секунд. +Временные сбои и возвращение к экрану с требующими обновления данными SHALL +обрабатываться автоматическим восстановлением по `frontend-data-resilience`. +Клиент MUST NOT предоставлять ручное обновление или повторную загрузку. -При отсутствии Client Cached Snapshot и ответе `404` клиент MUST показать недоступный счёт с возвратом к списку без действия повтора. При отсутствии Snapshot и `TBANK_NOT_CONNECTED`, `INVALID_TBANK_TOKEN`, `TBANK_UNAVAILABLE` либо иной ошибке клиент MUST показать безопасное error-состояние; повтор HTTP-чтения разрешён только для временной и неизвестной ошибки. +При первичной загрузке без успешного результата того же Brokerage Account +клиент MUST показывать структурный skeleton ожидаемого содержимого, включая +сводку, без вымышленных сумм. Область MUST иметь доступное обозначение занятости; +skeleton MUST NOT быть интерактивным или получать фокус. Неуспешное или +затянувшееся первичное получение SHALL переходить в локальную недоступность +по `frontend-loading-presentation`, а не в бесконечную загрузку. + +Истечение срока свежести MUST NOT скрывать доступный Client Cached Snapshot. +При обновлении и временной ошибке клиент MUST сохранять данные того же +Portfolio, включая валютные итоги, без индикации фоновой работы собственного +чтения, технических предупреждений об этом чтении и уведомлений. Исходное +`lastSuccessfulAt` и `isStale` MUST сохранять внутренний смысл: их показ или +скрытие MUST NOT делать сохранённый снимок новым успешным результатом или +подтверждением текущего доступа. Server-side stale и ошибка клиентского чтения +MUST NOT подменять друг друга. + +Состояние источника из `meta` — время последнего успешного результата, признак +устаревания и исход последней часовой попытки — не относится к собственному +чтению клиента: клиент MUST представлять его текстом либо доступно именованным +элементом по `frontend-data-resilience`, не превращая в действие пользователя. + +Клиент MUST показывать штатное empty-состояние только при подтверждённом полном +результате с пустыми `positions` и `cashBalances`, без вымышленных нулевых карточек. +Неполный итог MUST NOT выдаваться за окончательный; доступные независимые данные +MUST сохраняться на экране. При подтверждённом `404` клиент MUST скрыть личный +Portfolio и предложить возврат к списку счетов. При `TBANK_NOT_CONNECTED` или +`INVALID_TBANK_TOKEN` он MUST скрыть личные данные и предложить восстановление +подключения. Эти состояния MUST NOT маскироваться сохранённым снимком или +вызывать техническую повторную загрузку. #### Scenario: Показ устаревшего Portfolio -- **WHEN** клиент получает успешный ответ с `isStale: true` -- **THEN** клиент показывает Portfolio, исходное время и нейтральное предупреждение без ручной синхронизации - -#### Scenario: Первичная загрузка Portfolio -- **WHEN** клиент ожидает Portfolio без ранее полученного успешного ответа -- **THEN** он показывает структурный skeleton без вымышленных или нулевых сумм и без фокусируемых placeholders +- **WHEN** клиент получает успешный ответ с `isStale: true` в действующей границе доступа +- **THEN** клиент показывает позиции, остатки и доступные валютные итоги и обозначает устаревание источника текстом либо доступно именованным элементом, без ручного обновления #### Scenario: Неуспешная последняя попытка сохраняет Portfolio - **WHEN** клиент получает полный Portfolio и `meta` с неуспехом последней часовой попытки -- **THEN** он сохраняет Portfolio и исходное время видимыми и отдельно показывает текстовое либо доступно именованное предупреждение о неуспехе попытки +- **THEN** он сохраняет Portfolio и исходное время видимыми и отдельно показывает текстовое либо доступно именованное предупреждение о неуспехе попытки, не предлагая ручной повтор #### 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 #### Scenario: Обновление после истечения срока свежести -- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое HTTP-перечитывание -- **THEN** клиент сохраняет прежние валютные итоги и время, показывает неблокирующую индикацию и не заменяет данные skeleton-состоянием +- **WHEN** срок свежести показанного Portfolio истёк и началось фоновое обновление данных того же Brokerage Account +- **THEN** клиент сохраняет прежние валютные итоги без индикации обновления и не заменяет доступный состав и сводку skeleton-состоянием #### Scenario: Ошибка обновления после успешного ответа -- **WHEN** Client Cached Snapshot Portfolio уже видим, а повторное HTTP-чтение завершается ошибкой -- **THEN** клиент сохраняет прежние данные и время, показывает неблокирующее предупреждение и действие повтора чтения +- **WHEN** клиент уже показывает Client Cached Snapshot Portfolio, а обновление этих же данных завершается временной ошибкой +- **THEN** клиент сохраняет данные и суммы без предупреждения и действия повтора; ошибка не делает снимок свежим и не изменяет `isStale` #### Scenario: Ошибка клиентского обновления при server-side stale -- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое перечитывание завершается ошибкой -- **THEN** оба предупреждения остаются различимыми, а действие повтора относится только к HTTP-чтению +- **WHEN** клиент показывает Portfolio с `isStale: true`, а фоновое обновление этого же Portfolio завершается временной ошибкой +- **THEN** прежние суммы остаются видимыми без технических предупреждений и ручных действий, сохраняя различие внутренних состояний #### Scenario: Повторное обновление успешно -- **WHEN** после ошибки HTTP-чтения 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 567d8a0..9c0508e 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** клиент не показывает успех, сохраняет известный статус и локальную ошибку, возвращает применимые действия и не повторяет удаление автоматически -- 2.54.0