spec: archive loading skeleton change

This commit is contained in:
2026-08-27 21:43:57 +03:00
parent 2c9d163d39
commit 1d934e01d7
6 changed files with 405 additions and 0 deletions
@@ -0,0 +1,2 @@
schema: wealth-ledger-sdd
created: 2026-08-27
@@ -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. |
@@ -0,0 +1,41 @@
## Why
Текущие крупные индикаторы загрузки прерывают визуальный контекст и не дают
пользователю понять, какая часть интерфейса появится после завершения ожидания.
Для основных переходов и первичной загрузки нужен единый подход, который
сохраняет структуру экрана и делает ожидание менее резким.
## What Changes
- При первичной и частичной загрузке содержимого пользователь видит
placeholders, повторяющие ожидаемую структуру соответствующего экрана или
блока, вместо отдельного крупного индикатора процесса.
- Такой же принцип применяется во время загрузки отложенно открываемых
маршрутов и начального восстановления пользовательской сессии.
- Компактные индикаторы сохраняются для выполняющихся пользовательских
действий и фонового обновления уже показанных данных: в этих случаях
существующее содержимое или элемент управления остаются основным контекстом.
- Не меняются результаты загрузки, обработка ошибок и пустых состояний,
навигационные правила, операции с данными или доменное поведение.
## Capabilities
### New Capabilities
- `frontend-loading-presentation`: Единое наблюдаемое представление первичной и
частичной загрузки содержимого, загрузки маршрутов и восстановления сессии с
сохранением компактной индикации для действий и фонового обновления.
### Modified Capabilities
<!-- Existing capability requirements do not prescribe the loading presentation
being replaced; their data, cache, navigation and error semantics remain
unchanged. -->
## Impact
Изменение затрагивает пользователей frontend во время открытия экранов,
ожидания отдельных блоков и запуска приложения. Оно охватывает клиентское
представление loading-состояний, каноническое описание нового поведения и
пользовательскую as-built документацию; backend, API и доменная модель не
изменяются.
@@ -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
@@ -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 возвращён в ответственную фазу.