diff --git a/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/.openspec.yaml b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/.openspec.yaml new file mode 100644 index 0000000..f845845 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/.openspec.yaml @@ -0,0 +1,2 @@ +schema: wealth-ledger-sdd +created: 2026-08-27 diff --git a/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/design.md b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/design.md new file mode 100644 index 0000000..c33ed27 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/design.md @@ -0,0 +1,162 @@ +## Context + +Frontend уже разделяет запросные экраны на container, ViewModel и отдельные +представления состояний. ViewModel отличает начальную загрузку без данных от +фонового обновления Client Cached Snapshot, а pending-состояния действий +остаются у соответствующих форм и controls. Сейчас эти разные состояния +визуально смешаны: top-level loading views и route fallback используют общий +spinner, некоторые частичные блоки показывают loading-текст, а auth guards до +завершения bootstrap не рендерят ничего. + +Mantine уже является UI dependency и предоставляет Skeleton, layout и progress +primitives. Поэтому изменение должно остаться в presentation boundaries, +сохранить текущие ViewModel, query/cache и auth transitions и не вводить новый +loading framework. + +## Goals / Non-Goals + +**Goals:** + +- Сделать initial, partial, lazy-route и auth-bootstrap loading states + структурно похожими на ожидаемую область содержимого. +- Сохранить единое доступное обозначение занятой области и исключить skeleton + placeholders из интерактивной и focus-модели. +- Оставить уже показанные данные и controls основным контекстом для cached + refresh и pending mutations. +- Удержать ответственность за форму skeleton рядом с соответствующим app, + page или section view. + +**Non-Goals:** + +- Изменение запросов, cache timing, state selection, auth/route decisions, + error/empty states или доменных данных. +- Замена компактной индикации внутри pending controls и рядом с видимым Client + Cached Snapshot. +- Новый UI framework, глобальная orchestration загрузки или редизайн loaded + screens. +- Изменение backend, API contract или persistence. + +## Decisions + +### 1. Loading presentation принадлежит существующим view boundaries + +Top-level page loading остаётся отдельным state view, выбранным существующим +container по состоянию ViewModel. Каждый такой view строит skeleton из тех же +типов layout regions, что и соответствующий loaded screen: heading, cards, +filters, summaries или rows. Частичный skeleton принадлежит section view и +занимает только незагруженный блок, поэтому соседнее готовое содержимое не +скрывается. + +Это сохраняет установленную границу `container -> ViewModel -> view` и позволяет +форме placeholder следовать реальной семантике экрана. Альтернатива — заменить +общий loading StateView одним универсальным skeleton — отклонена: универсальная +форма не может одновременно правдоподобно представлять список счетов, +Portfolio, ledger и локальные sections и быстро разойдётся с loaded layouts. + +Общий StateView сохраняется для error и empty outcomes, но перестаёт владеть +initial loading. Его loading-вариант удаляется после перевода всех consumers, +чтобы spinner-based primary state нельзя было случайно продолжать использовать. + +### 2. Используются установленные Mantine primitives без новой абстракции + +Skeleton shapes собираются из Mantine Skeleton и текущих layout primitives. +Занятая область получает единый доступный status/busy contract и локализованное +имя, а сами визуальные placeholders остаются декоративными, не содержат fake +values и не создают focus targets. Custom animation не добавляется. + +Низкоуровневая shared-обёртка допустима только для повторяющейся busy-семантики; +структура cards, sections и rows остаётся в page/entity presentation layers. +Альтернативы в виде собственной CSS skeleton-системы или новой dependency +отклонены, потому что установленная библиотека уже покрывает нужные primitives +и снижает surface поддержки. + +### 3. Route loading и auth bootstrap остаются app-level concerns + +Suspense boundaries в routing layer получают neutral structural fallbacks для +ожидаемой области: auth-card shape для public auth routes, application-content +shape внутри доступного shell и neutral shell shape там, где сам shell ещё не +доступен. Уже отрисованный application shell не заменяется при загрузке +дочернего route chunk. + +Auth guards продолжают принимать решение только по существующему `ready` и +`user`. Пока `ready` ложен, они показывают neutral shell skeleton без profile, +navigation result или route content; после bootstrap текущий redirect/render +flow продолжается без изменений. Перенос bootstrap в router data APIs или +изменение auth ownership отклонены как несоразмерные визуальной задаче. + +### 4. Contextual progress не переводится на skeleton + +Button loading для submit/remove/update и компактные indicators фонового +обновления сохраняются. Когда Client Cached Snapshot существует, loaded/empty +view остаётся на экране, а текущая локальная refresh-индикация и recovery flow +продолжают работать рядом с ним. Skeleton выбирается только для состояния без +доступного результата либо для отдельно незагруженного content block. + +Так state choice остаётся детерминированным: наличие usable content имеет +приоритет над skeleton. Альтернатива показывать skeleton поверх cached data +отклонена, потому что она скрывает полезный контекст и смешивает initial loading +с background refresh. + +### 5. Проверяется наблюдаемая граница, а не разметка библиотеки + +Risk-based UI coverage фиксирует выбор skeleton для initial/partial и auth/route +loading, сохранение loaded context для refresh/pending states и отсутствие +интерактивности у placeholders. Тесты не закрепляют внутренний DOM Mantine или +точные декоративные размеры; layout fidelity подтверждается scoped +presentation review на существующих responsive boundaries. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | `affected` | App, page и section presentation boundaries используют структурные skeletons для initial/partial loading, сохраняя contextual progress и текущие state machines. | +| backend | `N/A` | Запросы, ответы, auth semantics, данные и server-side поведение не меняются. | +| specs | `affected` | Новый capability contract `frontend-loading-presentation` остаётся canonical описанием loading invariants. | +| docs | `affected` | После поставки as-built frontend и feature documentation отражает различие skeleton loading и contextual progress без копирования planning artifacts. | + +### Contract Gate Decision + +| Gate | Status | Reason | +| --- | --- | --- | +| runtime-openapi | `N/A` | Изменяется только frontend presentation; ни backend API, ни frontend contract snapshot не меняются. | + +## Risks / Trade-offs + +- **Skeleton расходится с loaded layout.** Page- и section-owned shapes следуют + тем же semantic regions и responsive containers; декоративные точные размеры + не превращаются в отдельный общий contract. +- **Skeleton ошибочно заменяет полезный cached context.** Существующая ViewModel + граница initial loading versus `isFetching` сохраняется, а contextual + indicators остаются отдельным presentation path. +- **Auth bootstrap показывает неверный пользовательский flow.** Neutral shell + не содержит profile или route outcome, а существующие guards остаются + единственным владельцем решения после `ready`. +- **Декоративные placeholders создают шум assistive technology.** Busy status + назначается области целиком; внутренние shapes не интерактивны и не выдают + fake content. +- **Больше специализированных view components.** Дополнительные небольшие views + приняты как цена точного соответствия разным экранам; общий generic skeleton + оставил бы меньше файлов, но больше условностей и visual drift. + +## Migration Plan + +1. Ввести app-level neutral shell/content fallbacks и согласовать общую + busy-семантику на существующих Mantine primitives. +2. Перевести top-level page loading views и partial section states на + принадлежащие им skeleton shapes, не меняя ViewModel и data flow. +3. Подключить route и auth-bootstrap fallbacks, сохранив текущие redirect и + session transitions. +4. Удалить устаревший generic spinner-loading path после отсутствия consumers; + compact mutation и cached-refresh indicators оставить без изменений. +5. После подтверждения поведения обновить durable as-built documentation. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | `required` | +| Reason | Auth decision не меняется, но новый neutral fallback отображается до завершения session bootstrap и поэтому должен иметь явно безопасный путь отката. | +| Trigger | Bootstrap fallback показывает public/protected route content до `ready`, нарушает redirect после определения сессии либо создаёт блокирующую accessibility regression. | +| Actions | Вернуть auth guards к прежнему blank fallback, сохранив существующие `ready`/`user` decisions; остальные page и section skeletons можно оставить независимо. | +| Data safety | Откат меняет только presentation; session tokens, query cache, backend data и persistence не преобразуются. | +| Verification | Подтвердить отсутствие route content до `ready`, прежние public/protected redirects после bootstrap и успешный frontend canonical gate. | diff --git a/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/proposal.md b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/proposal.md new file mode 100644 index 0000000..355e2dc --- /dev/null +++ b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/proposal.md @@ -0,0 +1,41 @@ +## Why + +Текущие крупные индикаторы загрузки прерывают визуальный контекст и не дают +пользователю понять, какая часть интерфейса появится после завершения ожидания. +Для основных переходов и первичной загрузки нужен единый подход, который +сохраняет структуру экрана и делает ожидание менее резким. + +## What Changes + +- При первичной и частичной загрузке содержимого пользователь видит + placeholders, повторяющие ожидаемую структуру соответствующего экрана или + блока, вместо отдельного крупного индикатора процесса. +- Такой же принцип применяется во время загрузки отложенно открываемых + маршрутов и начального восстановления пользовательской сессии. +- Компактные индикаторы сохраняются для выполняющихся пользовательских + действий и фонового обновления уже показанных данных: в этих случаях + существующее содержимое или элемент управления остаются основным контекстом. +- Не меняются результаты загрузки, обработка ошибок и пустых состояний, + навигационные правила, операции с данными или доменное поведение. + +## Capabilities + +### New Capabilities + +- `frontend-loading-presentation`: Единое наблюдаемое представление первичной и + частичной загрузки содержимого, загрузки маршрутов и восстановления сессии с + сохранением компактной индикации для действий и фонового обновления. + +### Modified Capabilities + + + +## Impact + +Изменение затрагивает пользователей frontend во время открытия экранов, +ожидания отдельных блоков и запуска приложения. Оно охватывает клиентское +представление loading-состояний, каноническое описание нового поведения и +пользовательскую as-built документацию; backend, API и доменная модель не +изменяются. diff --git a/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/specs/frontend-loading-presentation/spec.md b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/specs/frontend-loading-presentation/spec.md new file mode 100644 index 0000000..fe8459d --- /dev/null +++ b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/specs/frontend-loading-presentation/spec.md @@ -0,0 +1,73 @@ +## Purpose + +Определяет единое пользовательское представление ожидания во frontend, чтобы +структура будущего содержимого оставалась понятной без потери уже доступного +контекста. + +## ADDED Requirements + +### Requirement: Структурные placeholders для загрузки содержимого + +Frontend SHALL при первичной загрузке содержимого без доступного результата +показывать skeleton placeholders, которые передают расположение и визуальную +иерархию основных ожидаемых блоков. При частичной загрузке frontend MUST +ограничивать skeleton незагруженным блоком и сохранять уже доступные части +экрана видимыми. В этих состояниях frontend MUST NOT использовать отдельный +spinner как основное представление ожидаемого содержимого. + +Skeleton placeholders MUST NOT содержать правдоподобные пользовательские +данные, быть интерактивными или попадать в последовательность клавиатурного +фокуса; область ожидания MUST оставаться доступно обозначенной как занятая. + +#### Scenario: Экран впервые ожидает содержимое + +- **WHEN** User открывает экран, для которого ещё нет доступного результата +- **THEN** frontend показывает структурные skeleton placeholders основных блоков без отдельного spinner в роли основного loading-состояния + +#### Scenario: Загружается отдельный блок экрана + +- **WHEN** часть экрана уже доступна, а один из его содержательных блоков всё ещё ожидает результат +- **THEN** frontend сохраняет доступные части видимыми и показывает skeleton только в границах ожидающего блока + +#### Scenario: Пользователь перемещает клавиатурный фокус во время загрузки + +- **WHEN** на экране видны skeleton placeholders и User перемещает клавиатурный фокус +- **THEN** placeholders не получают фокус и не воспринимаются как доступные действия или реальные пользовательские данные + +### Requirement: Представление загрузки маршрута и восстановления сессии + +Frontend SHALL во время загрузки отложенно открываемого маршрута показывать +skeleton ожидаемой области содержимого, сохраняя уже доступный каркас +приложения. Во время начального восстановления пользовательской сессии +frontend MUST показывать нейтральный skeleton каркаса до определения доступного +пользователю потока и MUST NOT кратковременно показывать содержимое public или +protected маршрута как результат загрузки. В обоих случаях отдельный spinner +MUST NOT быть основным loading-состоянием. + +#### Scenario: Загружается отложенно открываемый маршрут + +- **WHEN** User переходит на маршрут, содержимое которого ещё загружается +- **THEN** frontend сохраняет доступный каркас приложения и показывает skeleton в ожидаемой области содержимого без отдельного spinner в роли основного loading-состояния + +#### Scenario: Приложение восстанавливает сессию при запуске + +- **WHEN** frontend ещё не определил результат начального восстановления пользовательской сессии +- **THEN** User видит нейтральный skeleton каркаса без преждевременного отображения public или protected содержимого + +### Requirement: Контекстная индикация действий и фонового обновления + +Frontend MUST сохранять уже доступное содержимое или элемент управления +основным контекстом, когда выполняется пользовательское действие либо фоновое +обновление Client Cached Snapshot. В этих состояниях frontend SHALL показывать +компактный progress indicator рядом с соответствующим контекстом и MUST NOT +заменять этот контекст skeleton placeholders. + +#### Scenario: Выполняется пользовательское действие + +- **WHEN** User запустил действие и его результат ещё ожидается +- **THEN** frontend сохраняет соответствующий элемент управления или окружающий контекст видимым, показывает компактную индикацию процесса и не заменяет интерфейс skeleton placeholders + +#### Scenario: Обновляется уже показанный snapshot + +- **WHEN** frontend обновляет данные при наличии видимого Client Cached Snapshot +- **THEN** frontend оставляет snapshot видимым, показывает компактную индикацию фонового обновления и не заменяет snapshot skeleton placeholders diff --git a/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/tasks.md b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/tasks.md new file mode 100644 index 0000000..da6cd27 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-replace-loading-spinners-with-skeletons/tasks.md @@ -0,0 +1,53 @@ +## 1. App-level loading boundaries + +- [x] 1.1 [frontend] Заменить route Suspense fallbacks на structural skeleton variants для public auth routes, ещё не загруженного application shell и content area внутри уже доступного shell. + - **Covers:** `frontend-loading-presentation` — `Представление загрузки маршрута и восстановления сессии` + - **Verification:** Route UI tests подтверждают подходящий skeleton fallback и сохранение уже отрисованного shell без проверки внутренней DOM-разметки Mantine. +- [x] 1.2 [frontend] Показывать neutral shell skeleton в public и protected auth guards до `ready`, сохранив существующие session decisions и redirects после bootstrap. + - **Covers:** `frontend-loading-presentation` — `Представление загрузки маршрута и восстановления сессии` + - **Verification:** Auth/route tests подтверждают отсутствие public и protected route content до `ready` и прежние redirects для authenticated и unauthenticated outcomes. + +## 2. Content loading presentation + +- [x] 2.1 [frontend] Реализовать page-owned initial skeleton views для Accounts и Portfolio с layout regions, соответствующими их loaded headings, cards, summaries и sections. + - **Covers:** `frontend-loading-presentation` — `Структурные placeholders для загрузки содержимого` + - **Verification:** Page UI tests подтверждают accessible busy state, отсутствие primary spinner/fake content/focus targets и сохранение существующих error, empty и loaded outcomes. +- [x] 2.2 [frontend] Реализовать page-owned initial skeleton views для Operations и Events с формой ожидаемых filters и rows, не меняя их ViewModel или request flow. + - **Covers:** `frontend-loading-presentation` — `Структурные placeholders для загрузки содержимого` + - **Verification:** Page UI tests подтверждают structural skeleton только при отсутствии результата и прежний переход к error, empty и loaded views. +- [x] 2.3 [frontend] Заменить loading-текст в recent operations, recent events и T-Bank connection на section-owned skeletons, ограниченные незагруженным блоком. + - **Covers:** `frontend-loading-presentation` — `Структурные placeholders для загрузки содержимого` + - **Verification:** Section UI tests подтверждают, что skeleton занимает только ожидающий блок, соседний page context остаётся видимым, а placeholders не интерактивны. +- [x] 2.4 [frontend] Удалить generic spinner-loading mode из StateView после миграции consumers и сохранить Button loading и локальные refresh indicators для pending mutations и Client Cached Snapshot. + - **Covers:** `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления` + - **Verification:** UI tests подтверждают сохранение controls или cached content вместе с compact progress и отсутствие skeleton replacement в mutation/refetch states. +- [x] 2.5 [frontend] Обновить frontend UI pattern documentation в соответствии с реализованной границей initial/partial skeleton versus contextual progress. + - **Covers:** `frontend-loading-presentation` — `Структурные placeholders для загрузки содержимого`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления` + - **Verification:** `frontend/docs/UI-PATTERN.md` описывает фактическое ownership loading views и не меняет data/auth/router semantics. + +## 3. Affected repository delivery + +- [x] 3.1 [frontend] Выполнить canonical frontend gate и scoped responsive/accessibility review всех новых app, page и section skeleton states. + - **Covers:** `N/A` — canonical gate и presentation review подтверждают delivery утверждённого frontend Design, но не вводят самостоятельное observable behavior. + - **Verification:** Под Node 22 выполнить `npm run verify` в `frontend`; проверить narrow и wide layouts, busy semantics, keyboard focus и rollback-safe auth bootstrap fallback. +- [x] 3.2 [docs] Обновить as-built system overview и затронутые authentication, accounts и Portfolio feature docs, описав поставленное различие structural skeleton и contextual progress. + - **Covers:** `frontend-loading-presentation` — `Структурные placeholders для загрузки содержимого`; `frontend-loading-presentation` — `Представление загрузки маршрута и восстановления сессии`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления` + - **Verification:** Affected docs описывают только фактический пользовательский flow, сохраняют локальные ссылки и не копируют Proposal, Delta Specs, Design или Tasks. +- [x] 3.3 [docs] Выполнить canonical docs gate после as-built updates. + - **Covers:** `N/A` — non-mutating docs gate проверяет affected delivery area и не добавляет новое product behavior. + - **Verification:** Под Node 22 выполнить `npm run verify` в `docs`. +- [x] 3.4 [specs] Сверить завершённую реализацию и отмеченные Tasks с каждым Requirement canonical delta, сохранив approved semantics либо маршрутизировав semantic mismatch обратно в Specification. + - **Covers:** `frontend-loading-presentation` — `Структурные placeholders для загрузки содержимого`; `frontend-loading-presentation` — `Представление загрузки маршрута и восстановления сессии`; `frontend-loading-presentation` — `Контекстная индикация действий и фонового обновления` + - **Verification:** Requirement-by-requirement comparison не оставляет unchecked implementation scope или behavior, отсутствующее в approved artifacts. +- [x] 3.5 [specs] Выполнить native strict validation change и canonical main specs. + - **Covers:** `N/A` — structural validation является canonical gate affected specs repository, а не самостоятельным пользовательским behavior. + - **Verification:** Выполнить `openspec validate replace-loading-spinners-with-skeletons --type change --strict --store wealth-ledger-specs` и `openspec validate --specs --strict --store wealth-ledger-specs`. + +## 4. Cross-area verification + +- [x] 4.1 [workspace] Семантически проверить frontend и docs delivery против Proposal, Delta Specs, Design и полного Tasks checklist после успешных affected-area gates. + - **Covers:** `N/A` — cross-repository semantic verification сопоставляет уже утверждённый scope и delivery, не владея implementation ни одной repository area. + - **Verification:** Зафиксирована requirement-by-requirement проверка initial/partial, route/auth и contextual-progress behavior без незапланированных отклонений. +- [x] 4.2 [workspace] Провести независимый read-only review завершённого change после semantic verification. + - **Covers:** `N/A` — independent review является обязательной cross-area completion проверкой и не создаёт product behavior или repository-owned implementation. + - **Verification:** Review охватывает implementation, canonical artifacts, accessibility, rollback boundary и affected docs; Critical и Important findings отсутствуют либо change возвращён в ответственную фазу. diff --git a/openspec/specs/frontend-loading-presentation/spec.md b/openspec/specs/frontend-loading-presentation/spec.md new file mode 100644 index 0000000..b7a9496 --- /dev/null +++ b/openspec/specs/frontend-loading-presentation/spec.md @@ -0,0 +1,74 @@ +# frontend-loading-presentation Specification + +## Purpose +Определяет единое пользовательское представление ожидания во frontend, чтобы +структура будущего содержимого оставалась понятной без потери уже доступного +контекста. + +## Requirements + +### Requirement: Структурные placeholders для загрузки содержимого + +Frontend SHALL при первичной загрузке содержимого без доступного результата +показывать skeleton placeholders, которые передают расположение и визуальную +иерархию основных ожидаемых блоков. При частичной загрузке frontend MUST +ограничивать skeleton незагруженным блоком и сохранять уже доступные части +экрана видимыми. В этих состояниях frontend MUST NOT использовать отдельный +spinner как основное представление ожидаемого содержимого. + +Skeleton placeholders MUST NOT содержать правдоподобные пользовательские +данные, быть интерактивными или попадать в последовательность клавиатурного +фокуса; область ожидания MUST оставаться доступно обозначенной как занятая. + +#### Scenario: Экран впервые ожидает содержимое + +- **WHEN** User открывает экран, для которого ещё нет доступного результата +- **THEN** frontend показывает структурные skeleton placeholders основных блоков без отдельного spinner в роли основного loading-состояния + +#### Scenario: Загружается отдельный блок экрана + +- **WHEN** часть экрана уже доступна, а один из его содержательных блоков всё ещё ожидает результат +- **THEN** frontend сохраняет доступные части видимыми и показывает skeleton только в границах ожидающего блока + +#### Scenario: Пользователь перемещает клавиатурный фокус во время загрузки + +- **WHEN** на экране видны skeleton placeholders и User перемещает клавиатурный фокус +- **THEN** placeholders не получают фокус и не воспринимаются как доступные действия или реальные пользовательские данные + +### Requirement: Представление загрузки маршрута и восстановления сессии + +Frontend SHALL во время загрузки отложенно открываемого маршрута показывать +skeleton ожидаемой области содержимого, сохраняя уже доступный каркас +приложения. Во время начального восстановления пользовательской сессии +frontend MUST показывать нейтральный skeleton каркаса до определения доступного +пользователю потока и MUST NOT кратковременно показывать содержимое public или +protected маршрута как результат загрузки. В обоих случаях отдельный spinner +MUST NOT быть основным loading-состоянием. + +#### Scenario: Загружается отложенно открываемый маршрут + +- **WHEN** User переходит на маршрут, содержимое которого ещё загружается +- **THEN** frontend сохраняет доступный каркас приложения и показывает skeleton в ожидаемой области содержимого без отдельного spinner в роли основного loading-состояния + +#### Scenario: Приложение восстанавливает сессию при запуске + +- **WHEN** frontend ещё не определил результат начального восстановления пользовательской сессии +- **THEN** User видит нейтральный skeleton каркаса без преждевременного отображения public или protected содержимого + +### Requirement: Контекстная индикация действий и фонового обновления + +Frontend MUST сохранять уже доступное содержимое или элемент управления +основным контекстом, когда выполняется пользовательское действие либо фоновое +обновление Client Cached Snapshot. В этих состояниях frontend SHALL показывать +компактный progress indicator рядом с соответствующим контекстом и MUST NOT +заменять этот контекст skeleton placeholders. + +#### Scenario: Выполняется пользовательское действие + +- **WHEN** User запустил действие и его результат ещё ожидается +- **THEN** frontend сохраняет соответствующий элемент управления или окружающий контекст видимым, показывает компактную индикацию процесса и не заменяет интерфейс skeleton placeholders + +#### Scenario: Обновляется уже показанный snapshot + +- **WHEN** frontend обновляет данные при наличии видимого Client Cached Snapshot +- **THEN** frontend оставляет snapshot видимым, показывает компактную индикацию фонового обновления и не заменяет snapshot skeleton placeholders