## 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. |