163 lines
12 KiB
Markdown
163 lines
12 KiB
Markdown
## 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. |
|